簡體   English   中英

下面的代碼是否捕獲了 TPL 中原始任務、延續任務和子任務的異常?

[英]Is the below code captures the exceptions from original, continuation and child tasks in TPL?

我正在使用 TPL 和 async/await 在 webclient 之上為我的應用程序構建異步 API。 很少有地方(通常我需要運行一堆異步任務並在最后等待所有任務的地方)遵循代碼片段。 我只是想確保我得到它的正確性,因為即使使用 TPL 編寫異步代碼相對容易,並且異步/等待調試/故障排除仍然具有挑戰性(客戶站點的交互式調試和故障排除問題) - 所以想要做對.

我的目標:能夠捕獲從原始任務、延續任務以及子任務生成的異常,以便我可以處理它(如果需要)。 我不希望任何例外被冷漠和遺忘。

我使用的基本原則: 1. .net 框架確保異常將附加到任務 2. Try/catch 塊可以應用於 async/await 以提供同步代碼的錯覺/可讀性(參考: http://channel9.msdn。 com/Events/TechDays/Techdays-2014-the-Netherlands/Async-programming-deep-divehttp://blogs.msdn.com/b/ericlippert/archive/2010/11/19/asyncchrony-in-c- 5-part-seven-exceptions.aspxhttp://msdn.microsoft.com/en-us/library/dd537614.aspx等)

問題:我想獲得批准,即我可以從原始任務、延續任務和子任務中捕獲異常的預期目標已經實現,並且我可以對示例進行任何改進:

例如,是否會出現其中一個組合任務(例如,未包裝的代理任務)根本不會被激活(waitforactivation 狀態),從而使 waitall 可能只是等待任務開始的情況? 我的理解是這些情況不應該發生,因為繼續任務總是執行,並返回一個使用 wnwrap 被代理跟蹤的任務。 只要我在所有層和 api 中遵循類似的模式,該模式就應該捕獲鏈式任務中的所有聚合異常。

注意:本質上是在尋找建議,例如如果原始任務狀態未完成,則避免在繼續任務中創建虛擬任務,或者使用附加到父級以便我只能等待父級等查看所有可能性,以便我可以選擇最好的選擇,因為這種模式嚴重依賴我的應用程序進行錯誤處理。

static void SyncAPIMethod(string[] args)
        {
            try
            {
                List<Task> composedTasks = new List<Task>();
                //the underlying async method follow the same pattern
                //either they chain the async tasks or, uses async/await 
                //wherever possible as its easy to read and write the code
                var task = FooAsync();
                composedTasks.Add(task);
                var taskContinuation = task.ContinueWith(t =>
                    {
                        //Intentionally not using TaskContinuationOptions, so that the 
                        //continuation task always runs - so that i can capture exception
                        //in case something is wrong in the continuation
                        List<Task> childTasks = new List<Task>();
                        if (t.Status == TaskStatus.RanToCompletion)
                        {

                            for (int i = 1; i <= 5; i++)
                            {
                                var childTask = FooAsync();
                                childTasks.Add(childTask);
                            }

                        }
                        //in case of faulted, it just returns dummy task whose status is set to 
                        //'RanToCompletion'
                        Task wa = Task.WhenAll(childTasks);
                        return wa;
                    });
                composedTasks.Add(taskContinuation);
                //the unwrapped task should capture the 'aggregated' exception from childtasks
                var unwrappedProxyTask = taskContinuation.Unwrap();
                composedTasks.Add(unwrappedProxyTask);
                //waiting on all tasks, so the exception will be thrown if any of the tasks fail
                Task.WaitAll(composedTasks.ToArray());
            }
            catch (AggregateException ag)
            {
                foreach(Exception ex in ag.Flatten().InnerExceptions)
                {
                    Console.WriteLine(ex);
                    //handle it
                }
            }
        }

來自評論:

IMO,這段代碼可以使用 async/await 更簡單和優雅。 我不明白您堅持使用 ContinueWith 和 Unwrap 的原因,以及您將內部和外部(未包裝的)任務都添加到composedTasks 的原因。

我的意思是像下面這樣。 我認為它做同樣的事情,你原來的代碼,但沒有的形式不必要的冗余composedTasksContinueWithUnwrap 如果您使用async/await您幾乎不需要它們。

static void Main(string[] args)
{
    Func<Task> doAsync = async () =>
    {
        await FooAsync().ConfigureAwait(false);

        List<Task> childTasks = new List<Task>();
        for (int i = 1; i <= 5; i++)
        {
            var childTask = FooAsync();
            childTasks.Add(childTask);
        }

        await Task.WhenAll(childTasks);
    };

    try
    {
        doAsync().Wait();
    }
    catch (AggregateException ag)
    {
        foreach (Exception ex in ag.Flatten().InnerExceptions)
        {
            Console.WriteLine(ex);
            //handle it
        }
    }
}

static async Task FooAsync()
{
    // simulate some CPU-bound work
    Thread.Sleep(1000); 
    // we could have avoided blocking like this:        
    // await Task.Run(() => Thread.Sleep(1000)).ConfigureAwait(false);

    // introduce asynchrony
    // FooAsync returns an incomplete Task to the caller here
    await Task.Delay(1000).ConfigureAwait(false);
}

更新以解決評論:

在某些用例中,我在“創建子任務”之后繼續調用更多“獨立”任務。

基本上,任何異步任務工作流都有三種常見場景:順序組合、並行組合或這兩者的任意組合(混合組合):

  • 順序組合:

     await task1; await task2; await task3;
  • 平行組合:

     await Task.WhenAll(task1, task2, task3); // or await Task.WhenAny(task1, task2, task3);
  • 混合成分:

     var func4 = new Func<Task>(async () => { await task2; await task3; }); await Task.WhenAll(task1, func4());

如果上述任何任務執行 CPU 密集型工作,您可以使用Task.Run ,例如:

    var task1 = Task.Run(() => CalcPi(numOfPiDigits));

其中CalcPi是一種進行實際計算的同步方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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