[英]Thread-safe task queue await
我想做一件簡單的事情(假設ContinueWith
是線程安全的):
readonly Task _task = Task.CompletedTask;
// thread A: conditionally prolong the task
if(condition)
_task.ContinueWith(o => ...);
// thread B: await for everything
await _task;
問題:在上面的代碼中, await _task
立即返回,忽略是否有內部任務。
_task
可以延長多個ContinueWith
的擴展要求,我將如何等待所有這些都完成?
當然,我可以嘗試以舊的線程安全方式進行操作:
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;
或者通過將任務存儲在線程安全集合中並使用Task.WaitAll
。
但我很好奇第一個片段是否有一個簡單的修復方法?
您的舊版本是可以的,除了在保持鎖定時await
; 您應該在持有鎖的同時將該_task
復制到局部變量,釋放鎖,然后才await
但是,ContinueWith 工作流程不是 IMO 實現該邏輯的最佳方式。 在 async-await 成為語言的一部分之前,在 .NET 4.0 中引入了 ContinueWith。
在現代 C# 中,我認為最好這樣做:
static async Task runTasks()
{
if( condition1 )
await handle1();
if( condition2 )
await handle2();
}
readonly Task _task = runTasks();
或者,如果您希望它們並行運行:
IEnumerable<Task> parallelTasks()
{
if( condition1 )
yield return handle1();
if( condition2 )
yield return handle2();
}
readonly Task _task = Task.WhenAll( parallelTasks() );
PS 如果條件是動態變化的,對於順序版本,您應該在第一次等待之前將它們的值復制到局部變量。
更新:所以你是說你的線程 A 延長了該任務以響應一些動態發生的事件? 如果是,您的舊方法沒問題,它很簡單並且有效,只需修復那個lock(){ await... }
並發錯誤。
從理論上講,更清潔的方法可能類似於反應器模式,但不幸的是它要復雜得多。 這是我的開源示例,有點類似。 您不需要並發限制信號量,也不需要第二個隊列,但您需要公開一個 Task 類型的屬性,用TaskCompletionSource<bool>
對其進行初始化,當第一個是發布,並在沒有更多發布的任務后在 runAsyncProcessor 方法中完成該任務。
在您的原始代碼await _task;
立即返回,因為task
已完成。 你需要做
_task = _task.ContinueWith(...);
您不必使用鎖。 您正在同一個線程中工作。 在代碼的“鎖定”版本中,您確實使用_task = _task.ContinueWith(...)
這就是它起作用的原因,而不是因為鎖定。
編輯:剛剛看到你想做_task = _task.ContinueWith(...);
在一個線程和_task = _task.ContinueWith(...)
。
這作為設計是相當糟糕的。 你永遠不應該將線程和任務結合起來。 對於僅任務解決方案或僅線程解決方案,您應該使用 go。
如果您 go 用於僅任務解決方案(推薦),只需創建一個良好的工作任務序列,然后在每個任務上根據前一個任務的結果決定如何進行。
如果您將 go 用於純線程解決方案,您可能希望使用ManualResetEvent
句柄來同步您的任務。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.