Task.WhenAll 未完成

Task.WhenAll not completing

我是 C# 中的任务的新手,我遇到了一个我不明白的问题。在下面的代码中,有人可以解释为什么 Unsubscribe 方法末尾的 WriteLine 永远不会被调用。它似乎与前面的 foreach 循环中的 ContinueWith 有关,就好像我评论说它工作正常一样。 谢谢

using System;
using System.Threading;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {

        Task.Run(() => Unsubscribe());

        Console.WriteLine("Method: Main. End");

        Console.ReadKey();

    }

    private static void Unsubscribe()
    {
        Dictionary<int, string> dicPairsBySubID = new Dictionary<int, string>();

        dicPairsBySubID.Add(1, "A");
        dicPairsBySubID.Add(2, "B");
        dicPairsBySubID.Add(3, "C");
        dicPairsBySubID.Add(4, "D");
        dicPairsBySubID.Add(5, "E");

        List<Task> taskList = new List<Task>();

        foreach (KeyValuePair<int, string> sub in dicPairsBySubID)
        {
            int chanID = sub.Key;

            Task task = Task.Run(() => SendUnsubscribe(chanID))
            .ContinueWith(tsk =>
            {
                var flattened = tsk.Exception.Flatten();

                flattened.Handle(ex =>
                {
                    Console.WriteLine(string.Format("Error unsubscribing pair {0} (chanID = {2})", sub.Value, chanID), ex);
                    return true;
                });

            }, TaskContinuationOptions.OnlyOnFaulted);

            taskList.Add(task);
        }

        Task.WhenAll(taskList).Wait();

        // WHY DOES THIS NEVER GET RUN?
        Console.WriteLine("Method: Unsubscribe. End");


    }

    private static async Task SendUnsubscribe(int chanID)
    {


        await SendAsync(chanID);


        Console.WriteLine("Method: SendUnsubscribe. Chan ID: " + chanID);


    }

    private static async Task SendAsync(int chanID)
    {

        await Task.Run(() => Thread.Sleep(1000));
        Console.WriteLine("Method: SendAsync. Chan ID: " + chanID);

    } 


}

我对此进行了测试。我在 WhenAll 处遇到异常,说任务已取消。这使得所有这些都有意义:

  1. 正如 Default 的回答所指出的,您正在将 ContinueWith 任务添加到 taskList
  2. ContinueWith 任务被取消,因为它应该 运行 OnlyOnFaulted,并且没有发生异常。
  3. 由于抛出异常(未处理),您的 Unsubscribe 的其余部分不是 运行。

您可以采取以下措施来改善这一点:

  1. 不要仅对 运行 异步方法使用 Task.Run。您可以这样做:

    taskList.Add(SendUnsubscribe(chanID));

  2. 在您的 SendUnsubscribe 方法中使用 try/catch,而不是在任务中使用 ContinueWith

  3. 等待 WhenAll 而不是使用 Wait():

    await Task.WhenAll(taskList);

如果您认为 SendUnsubscribe 无法处理任何可能发生的异常,您总是可以将 await Task.WhenAll(taskList); 包装在 try/catch 块中。

你的任务抛出一个你没有捕捉到的异常,因为:

  • ContinueWith 也是 return 一个任务(然后放在 taskList 中)
  • 您已声明当前一个任务出现故障时(由于 TaskContinuationOptions.OnlyOnFaulted),该任务仅应 运行
  • 你给出的例子没有进入故障状态
  • 当 ContinueWith 任务由于这些选项而不是 运行 时 enters the state Cancelled
  • 等待取消的任务抛出异常
  • 在您的 Main 方法中,Task.Run(() => Unsubscribe()) 调用的 return 值未被 await 编辑,因此从未被注意到。

进一步:

  • 您不应在任务中使用 Wait()。您已经开始使用 asyncawait,请在您的程序中使用它们。 真正使用 Wait() 的唯一原因是你肯定不能使用异步。尽量避免它。

  • async 时,始终使用后缀 Async 命名您的方法。这表明使用该方法的用户有明确的意图。