Task.WaitAll 和异常

32

我在异常处理和并行任务方面遇到了问题。

下面的代码启动了两个任务并等待它们完成。我的问题是,如果有一个任务抛出异常,则永远无法到达catch处理程序。

        List<Task> tasks = new List<Task>();
        try
        {                
            tasks.Add(Task.Factory.StartNew(TaskMethod1));
            tasks.Add(Task.Factory.StartNew(TaskMethod2));

            var arr = tasks.ToArray();                
            Task.WaitAll(arr);
        }
        catch (AggregateException e)
        {
            // do something
        }

但是,当我使用以下代码来等待任务并设置超时时,异常被捕获。

 while(!Task.WaitAll(arr,100));

我似乎缺少了某些东西,因为WaitAll的文档描述我的第一次尝试是正确的。请帮助我理解为什么它不起作用。


1
TaskMethod1和TaskMethod2是做什么的?你正在执行哪个线程?如果您可以将其转换为一个简短但完整的示例(就像我的答案),那将非常有帮助。 - Jon Skeet
3个回答

29

无法重现此问题 - 对于我来说它正常工作:

using System;
using System.Threading;
using System.Threading.Tasks;

class Test
{
    static void Main()
    {
        Task t1 = Task.Factory.StartNew(() => Thread.Sleep(1000));
        Task t2 = Task.Factory.StartNew(() => {
            Thread.Sleep(500);
            throw new Exception("Oops");
        });

        try
        {
            Task.WaitAll(t1, t2);
            Console.WriteLine("All done");
        }
        catch (AggregateException)
        {
            Console.WriteLine("Something went wrong");
        }
    }
}

这将打印出“Something went wrong”,正如我所预期的那样。

你有没有可能有一个任务没有完成?WaitAll确实会等待所有任务完成,即使其中一些已经失败了。


2
谢谢您的快速回答,Jon!我的问题是另一个任务依赖于失败的任务,因此它会无限等待失败的任务。我的想法是,当任务失败时立即捕获异常,但情况并非如此。感谢您指出这一点。 - thumbmunkeys
6
请看这里的“创建任务续延”来明确表达依赖关系 - http://msdn.microsoft.com/en-us/library/dd537609.aspx - Steve Townsend
@thumbmunkeys 没有意识到这个帖子已经发布了。这正是我在这里提出的问题:https://stackoverflow.com/q/47820918/695964 - KFL

12

以下是我解决问题的方式,正如我的答案/问题上方的评论所暗示的:

调用者捕获由障碍协调的任务引发的任何异常,并使用强制取消信号通知其他任务:

CancellationTokenSource cancelSignal = new CancellationTokenSource();
try
{
    // do work
    List<Task> workerTasks = new List<Task>();
    foreach (Worker w in someArray)
    {
        workerTasks.Add(w.DoAsyncWork(cancelSignal.Token);
    }
    while (!Task.WaitAll(workerTasks.ToArray(), 100, cancelSignal.Token)) ;

 }
 catch (Exception)
 {
     cancelSignal.Cancel();
     throw;
 }

2
很不错。从未意识到 CancellationTokenSource 还有这种用法。 - trailmax

0
我试图为集合中的每个项创建一个调用,结果是这样的:
var parent = Task.Factory.StartNew(() => {
  foreach (var acct in AccountList)
    {
      var currAcctNo = acct.Number;
      Task.Factory.StartNew(() =>
      {
        MyLocalList.AddRange(ProcessThisAccount(currAcctNo));
      }, TaskCreationOptions.AttachedToParent);
      Thread.Sleep(50);
    }
  });

我不得不在每次添加子任务后添加Thread.Sleep,因为如果我不这样做,进程会倾向于用下一个迭代覆盖currAcctNo。 我的列表中会有3或4个不同的账号,当它处理每个账号时,ProcessThisAccount调用将为所有调用显示最后一个账号。 一旦我放入Sleep,进程就可以很好地工作。

可能会感兴趣的内容:https://dev59.com/tW445IYBdhLWcg3wKHAJ - thumbmunkeys
Parallel.ForEach更高效吗? - Kiquenet
1
这个回答和原问题有什么关系?没有例外。 - knocte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接