簡體   English   中英

如何從.NET 4.5中的並行任務中獲益

[英]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()的結果),您可能希望將foreachComplete()調用移動到單獨的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM