[英]How to yield from parallel tasks in .NET 4.5
我想使用.NET迭代器和並行Tasks / await?。 像這樣的東西:
IEnumerable<TDst> Foo<TSrc, TDest>(IEnumerable<TSrc> source)
{
Parallel.ForEach(
source,
s=>
{
// Ordering is NOT important
// items can be yielded as soon as they are done
yield return ExecuteOrDownloadSomething(s);
}
}
不幸的是.NET無法原生地處理這個問題。 到目前為止@svick的最佳答案 - 使用AsParallel()。
獎勵:任何實現多個發布者和單個訂閱者的簡單異步/等待代碼? 訂閱者將屈服,並且pubs將處理。 (僅核心庫)
這似乎是PLINQ的工作:
return source.AsParallel().Select(s => ExecuteOrDownloadSomething(s));
這將使用有限數量的線程並行執行委托,並在完成后立即返回每個結果。
如果ExecuteOrDownloadSomething()
方法是IO綁定的(例如它實際下載了一些東西)並且你不想浪費線程,那么使用async
- await
可能有意義,但它會更復雜。
如果你想充分利用async
,你不應該返回IEnumerable
,因為它是同步的(即如果沒有可用的項,它會阻塞)。 你需要的是某種異步集合,你可以使用TPL Dataflow的ISourceBlock
(特別是TransformBlock
):
ISourceBlock<TDst> Foo<TSrc, TDest>(IEnumerable<TSrc> source)
{
var block = new TransformBlock<TSrc, TDest>(
async s => await ExecuteOrDownloadSomethingAsync(s),
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
});
foreach (var item in source)
block.Post(item);
block.Complete();
return block;
}
如果源是“慢”(即您希望在迭代source
完成之前開始處理來自Foo()
的結果),您可能希望將foreach
和Complete()
調用移動到單獨的Task
。 更好的解決方案是將source
轉換為ISourceBlock<TSrc>
。
因此,您真正想要做的就是根據完成時間順序排列一系列任務。 這不是非常復雜:
public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
var input = tasks.ToList();
var output = input.Select(task => new TaskCompletionSource<T>());
var collection = new BlockingCollection<TaskCompletionSource<T>>();
foreach (var tcs in output)
collection.Add(tcs);
foreach (var task in input)
{
task.ContinueWith(t =>
{
var tcs = collection.Take();
switch (task.Status)
{
case TaskStatus.Canceled:
tcs.TrySetCanceled();
break;
case TaskStatus.Faulted:
tcs.TrySetException(task.Exception.InnerExceptions);
break;
case TaskStatus.RanToCompletion:
tcs.TrySetResult(task.Result);
break;
}
}
, CancellationToken.None
, TaskContinuationOptions.ExecuteSynchronously
, TaskScheduler.Default);
}
return output.Select(tcs => tcs.Task);
}
所以這里我們為每個輸入任務創建一個TaskCompletionSource
,然后遍歷每個任務並設置一個繼續,它從BlockingCollection
獲取下一個完成源並設置它的結果。 完成的第一個任務抓取返回的第一個tcs,第二個任務完成獲取返回的第二個tcs,依此類推。
現在您的代碼變得非常簡單:
var tasks = collection.Select(item => LongRunningOperationThatReturnsTask(item))
.Order();
foreach(var task in tasks)
{
var result = task.Result;//or you could `await` each result
//....
}
在MS機器人團隊制作的異步庫中,它們具有並發原語,允許使用迭代器生成異步代碼。
圖書館(CCR)是免費的(它不是免費的)。 這里有一篇很好的介紹性文章: 並發事務
也許你可以將這個庫與.Net任務庫一起使用,或者它會激勵你“自己動手”
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.