简体   繁体   English

线程安全的任务队列等待

[英]Thread-safe task queue await

I want to do a simple thing (assuming that ContinueWith is thread-safe ):我想做一件简单的事情(假设ContinueWith线程安全的):

readonly Task _task = Task.CompletedTask;

// thread A: conditionally prolong the task
if(condition)
    _task.ContinueWith(o => ...);

// thread B: await for everything
await _task;

Problem: in above code await _task immediately returns, disregards if there are inner tasks.问题:在上面的代码中, await _task立即返回,忽略是否有内部任务。

Extending requirement that _task can be prolonged by multiple ContinueWith , how would I await for all of them to finish? _task可以延长多个ContinueWith的扩展要求,我将如何等待所有这些都完成?


Of course I can try to do it in old thread-safe way:当然,我可以尝试以旧的线程安全方式进行操作:

Task _task = Task.CompletedTask;
readonly object _lock = new object();

// thread A
if(condition)
    _lock(_lock)
        _task = _task.ContinueWith(o => ...); // storing the latest inner task

// thread B
lock(_lock)
    await _task;

Or by storing tasks in thread-safe collection and using Task.WaitAll .或者通过将任务存储在线程安全集合中并使用Task.WaitAll

But I was curious if there is an easy fix to a first snippet?但我很好奇第一个片段是否有一个简单的修复方法?

Your old version is OK, except await while keeping the lock;您的旧版本是可以的,除了在保持锁定时await you should copy that _task to a local variable while holding the lock, release the lock, and only then await您应该在持有锁的同时将该_task复制到局部变量,释放锁,然后才await

But that ContinueWith workflow is IMO not the best way to implement that logic.但是,ContinueWith 工作流程不是 IMO 实现该逻辑的最佳方式。 ContinueWith was introduced in .NET 4.0, before async-await became part of the language.在 async-await 成为语言的一部分之前,在 .NET 4.0 中引入了 ContinueWith。

In modern C#, I think it's better to do something like this:在现代 C# 中,我认为最好这样做:

static async Task runTasks()
{
    if( condition1 )
        await handle1();
    if( condition2 )
        await handle2();
}
readonly Task _task = runTasks();

Or if you want them to run in parallel:或者,如果您希望它们并行运行:

IEnumerable<Task> parallelTasks()
{
    if( condition1 )
        yield return handle1();
    if( condition2 )
        yield return handle2();
}
readonly Task _task = Task.WhenAll( parallelTasks() );

PS If the conditions are changing dynamically, for the sequential version you should copy their values to local variables, before the first await. PS 如果条件是动态变化的,对于顺序版本,您应该在第一次等待之前将它们的值复制到局部变量。

Update: So you are saying your thread A prolongs that task in response to some events which happen dynamically?更新:所以你是说你的线程 A 延长了该任务以响应一些动态发生的事件? If yes, your old method is OK, it's simple and it works, just fix that lock(){ await... } concurrency bug.如果是,您的旧方法没问题,它很简单并且有效,只需修复那个lock(){ await... }并发错误。

Theoretically, the cleaner approach might be something like a reactor pattern, but it's way more complicated unfortunately.从理论上讲,更清洁的方法可能类似于反应器模式,但不幸的是它要复杂得多。 Here's my open source example of something slightly similar.这是我的开源示例,有点类似。 You don't need the concurrency limit semaphore nor the second queue, but you need to expose a property of type Task, initialize it with CompletedTask, replace with a new task created by TaskCompletionSource<bool> from the post method when the first one is posted, and complete that task in runAsyncProcessor method once there're no more posted tasks.您不需要并发限制信号量,也不需要第二个队列,但您需要公开一个 Task 类型的属性,用TaskCompletionSource<bool>对其进行初始化,当第一个是发布,并在没有更多发布的任务后在 runAsyncProcessor 方法中完成该任务。

In your original code await _task;在您的原始代码await _task; returns immediately because task is completed.立即返回,因为task已完成。 You need to do你需要做

_task = _task.ContinueWith(...);

You don't have to use locks.您不必使用锁。 You are working in the same thread.您正在同一个线程中工作。 In your "locks" version of the code, you do use _task = _task.ContinueWith(...) and that is why it works, not because of the locks.在代码的“锁定”版本中,您确实使用_task = _task.ContinueWith(...)这就是它起作用的原因,而不是因为锁定。


EDIT: Just saw that you want to do _task = _task.ContinueWith(...);编辑:刚刚看到你想做_task = _task.ContinueWith(...); in one thread and _task = _task.ContinueWith(...) .在一个线程和_task = _task.ContinueWith(...)

This is quite bad as design.这作为设计是相当糟糕的。 You should never combine threads and tasks.你永远不应该将线程和任务结合起来。 You should either go for a task-only solution, or for a thread-only solution.对于仅任务解决方案或仅线程解决方案,您应该使用 go。

If you go for a task-only solution (recommended), just create a good sequence of work tasks and then on each task decide how to proceed based on the result of the previous task(s).如果您 go 用于仅任务解决方案(推荐),只需创建一个良好的工作任务序列,然后在每个任务上根据前一个任务的结果决定如何进行。

If you go for a thread-only solution, you might want to use ManualResetEvent handles to synchronize your tasks.如果您将 go 用于纯线程解决方案,您可能希望使用ManualResetEvent句柄来同步您的任务。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM