C# System.Threading.Tasks-无法使用Task.WaitAll()捕获异常

C# System.Threading.Tasks-无法使用Task.WaitAll()捕获异常,c#,C#,我试图并行运行一些任务,每个任务都返回一个值。一旦它们全部完成,我想利用这些返回的结果。我遇到的问题是,如果某个任务抛出异常,则捕获该异常 根据我在其他一些SO问题上看到的内容,我认为下面的简化代码应该可以工作。但是,当我在VisualStudio中运行它时,我的异常不会得到处理 using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System

我试图并行运行一些任务,每个任务都返回一个值。一旦它们全部完成,我想利用这些返回的结果。我遇到的问题是,如果某个任务抛出异常,则捕获该异常

根据我在其他一些SO问题上看到的内容,我认为下面的简化代码应该可以工作。但是,当我在VisualStudio中运行它时,我的异常不会得到处理

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

class Program
    {
        static void Main()
        {
            int[] ids = { 1, 2, 3, 4 };
            Test t = new Test();
             t.Process(ids);
        }
    }

    class Test
    {
        public void Process(int[] ids)
        {
            var tasks = new List<Task<Tuple<int, bool>>>(ids.Count());
            foreach (int id in ids)
            {
                //The task will run at an indeterminate time so create a copy of id as it may have a new value assigned before that occurs 
                int i = id;
                var task = Task.Factory.StartNew(() => DoWork(i));
                tasks.Add(task);
            }
            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch (AggregateException ex)
            {
                foreach (var innerEx in ex.InnerExceptions)
                {
                    Console.WriteLine(innerEx);
                }

            }
            foreach (var t in tasks)
            {
                Tuple<int, bool> result = t.Result;

                Console.WriteLine(string.Format("{0} - success = {1}", result.Item1, result.Item2));
            }
        }

        private Tuple<int, bool> DoWork(int i)
        {

            Console.WriteLine(string.Format("Processing {0}", i));
            Thread.Sleep(i * 500);
            if (i == 3)
            {
                //This exception does not get caught.
                throw new Exception(string.Format("exception thrown on: {0}", i));
            }
            return new Tuple<int, bool>(i, true);
        }
    }
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用System.Threading.Tasks;
使用系统线程;
班级计划
{
静态void Main()
{
int[]id={1,2,3,4};
测试t=新测试();
t、 进程(ids);
}
}
课堂测试
{
公共无效进程(int[]id)
{
var tasks=新列表(ids.Count());
foreach(id中的int-id)
{
//任务将在不确定的时间运行,因此创建id的副本,因为在此之前可能已为其分配了新值
int i=id;
var task=task.Factory.StartNew(()=>DoWork(i));
任务。添加(任务);
}
尝试
{
Task.WaitAll(tasks.ToArray());
}
捕获(聚合异常)
{
foreach(ex.InnerExceptions中的var innerEx)
{
控制台写入线(innerEx);
}
}
foreach(任务中的var t)
{
Tuple result=t.result;
WriteLine(string.Format(“{0}-success={1}”,result.Item1,result.Item2));
}
}
私有元组DoWork(int i)
{
WriteLine(string.Format(“处理{0}”,i));
睡眠(i*500);
如果(i==3)
{
//此异常不会被捕获。
抛出新异常(string.Format(“在{0}上抛出的异常”,i));
}
返回新的元组(i,true);
}
}
我所期望的应该打印出来(由于并行运行,订单可能会更改):

处理1
处理2
处理3
处理4
异常引发:3
1-成功=正确
2-成功=正确
4-成功=正确


我是否完全误解了Task.WaitAll的工作原理?

不,你不是。但是,您将观察异常两次。Task.WaitAll将引发异常,您的代码将输出有关异常的信息。然后,当您从t.result(任务仍然包含引发异常的任务)获取该任务的结果时,您将再次获得异常

要解决此问题,您至少有两种选择:

  • 从任务列表中删除生成异常的任务 任务
  • 删除你的Task.WaitAll,然后在 期望t.Result将阻止的任务,直到完成该任务为止 完成(如果该任务引发异常,则引发异常)
  • 以下是#2的更新:

    公共作废流程(int[]id)
    {
    var tasks=新列表(ids.Count());
    foreach(id中的int-id)
    {
    //任务将在不确定的时间运行,因此创建id的副本,因为在此之前可能已为其分配了新值
    int i=id;
    var task=task.Factory.StartNew(()=>DoWork(i));
    任务。添加(任务);
    }
    foreach(任务中的var t)
    {
    尝试
    {
    Tuple result=t.result;
    WriteLine(string.Format(“{0}-success={1}”,result.Item1,result.Item2));
    }
    捕获(聚合异常)
    {
    foreach(ex.InnerExceptions中的var innerEx)
    {
    Console.WriteLine(innerEx.Message);
    }
    }
    }
    }
    
    是-如果要停止正在运行的任务,则需要执行取消,否则它们将如何在“良好状态”下停止?不要对控制流使用异常。检查Task.Exception。如果你知道结果会丢,就不要让它丢。不过,+1.小心处理Task.Exception。它只报告已经发生的异常——它不是阻塞调用。在访问t.Result之前添加t.Exception为null的检查不会改变示例中程序的行为。WaitAll将等待所有操作,但我忽略了您的代码没有使用它。那你是对的。
    public void Process(int[] ids)
    {
        var tasks = new List<Task<Tuple<int, bool>>>(ids.Count());
        foreach (int id in ids)
        {
            //The task will run at an indeterminate time so create a copy of id as it may have a new value assigned before that occurs 
            int i = id;
            var task = Task.Factory.StartNew(() => DoWork(i));
            tasks.Add(task);
        }
        foreach (var t in tasks)
        {
            try
            {
                Tuple<int, bool> result = t.Result;
    
                Console.WriteLine(string.Format("{0} - success = {1}", result.Item1, result.Item2));
            }
            catch (AggregateException ex)
            {
                foreach (var innerEx in ex.InnerExceptions)
                {
                    Console.WriteLine(innerEx.Message);
                }
            }
        }
    }