【问题标题】:Task.WhenAll not completing [duplicate]Task.WhenAll 未完成 [重复]
【发布时间】:2018-01-18 16:38:15
【问题描述】:

我是 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);

    } 


}

【问题讨论】:

  • 如果您要使用异步,请正确使用。使用带有 async main 的 C# 7.1。不要使用.ContinueWith().Wait()。这些是代码气味。您需要 await 异步调用。阅读Async/Await - Best Practices in Asynchronous Programming
  • 您不需要所有这些Task.Runs。特别是,当 SendUnsubscribe 已经是一个任务并且甚至不等待它的结果或异常时,将它包装在一个中的意义何在? Task.WhenAll(taskList).Wait(); 也可以替换为 await Task.WhenAll(taskList); 声明 private async Task Unsubscribe()

标签: c# multithreading task task-parallel-library


【解决方案1】:

您的任务引发了您没有捕获的异常,因为:

  • ContinueWith 还返回一个任务(然后放在taskList 中)
  • 您已声明该任务仅应在前一个出现故障时运行(由于TaskContinuationOptions.OnlyOnFaulted
  • 您给出的示例没有进入故障状态。
  • 当 ContinueWith 任务由于这些选项而未运行时,enters the state Cancelled
  • 等待取消的任务会引发异常
  • 在您的 Main 方法中,Task.Run(() =&gt; Unsubscribe()) 调用的返回值不是 awaited,因此它永远不会被注意到。

进一步:

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

  • 当您的方法为async 时,始终使用后缀Async 命名您的方法。这显示了使用该方法的用户的明确意图。

【讨论】:

  • 这与问题无关。将延续标记为OnlyOnFaulted 并不意味着如果前件没有故障,它就永远不会完成,它只是意味着委托没有运行并且任务无论如何都被标记为完成。问题不是问为什么该延续中的代码没有运行,而是问为什么WhenAll 永远不会完成,这与延续是否只在错误任务上运行或这些任务是否错误无关。
  • @Servy 感谢您的意见!在我实际尝试了代码之后,我编辑了这个问题。现在应该更正确(并且有一个给定的例子)
  • @Servy 答案还是有问题吗?
  • 谢谢你的默认,并感谢'进一步',我很感激你的建议。我想我正在慢慢开始掌握 Tasks 和 async/await 的窍门。
【解决方案2】:

我对此进行了测试。我在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 块中。

【讨论】:

    猜你喜欢
    • 2016-01-30
    • 2021-07-17
    • 1970-01-01
    • 2018-08-02
    • 2014-09-08
    • 2014-02-14
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多