3

每次以下运行时,都会捕获 Mike 的异常。

WhenAll 是顺序的,涉及每个任务之间的延续上下文,还是所有任务都同时运行?如果它是并发的,为什么 Mike 的异常总是被捕获而不是 Mitch 的。我推迟了迈克,以便给米奇一个机会。如果它是连续的,那么使它并发涉及什么?发出 Web 请求/进行文件处理时会应用并发执行吗?

假设这段代码更严重,这是否是一种明智的异步方法?该场景将是几种方法 - Jason、Mitch 和 Mike - 在没有阻塞的情况下同时运行并在全部完成后继续事件处理程序?关于异常处理的幼稚实现,我应该注意哪些注意事项?有什么需要注意的问题或潜在问题吗?

private async void button1_Click(object sender,EventArgs e)
{
    try
    {
        AsyncJason c1 = new AsyncJason();
        await c1.Hello();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

public class AsyncJason
{
    public AsyncJason()
    {
    }

    public async Task Hello()
    {
        var j = await GetJasonAsync();
        string[] dankeSchon = await Task.WhenAll(new Task<string>[] {GetJasonAsync(), GetMikeAsync(), GetMitchAsync()});
    }

    private async Task<string> GetJasonAsync()
    {
        var result = await Task.Run<string>(() => GetJason());
        return result;
    }

    private string GetJason()
    {
        return "Jason";
    }

     private async Task<string> GetMitchAsync()
    {
        var result = await Task.Run<string>(() => GetMitch());
        return result;
    }

    private string GetMitch()
    {
        throw new ArgumentException("Mitch is an idiot", "none");
    }

     private async Task<string> GetMikeAsync()
    {
        await Task.Delay(3000);
        var result = await Task.Run<string>(() => GetMike());
        return result;
    }

    private string GetMike()
    {
        throw new ArgumentException("Mike is an idiot", "none");
    }
}
4

1 回答 1

8

WhenAll 是顺序的还是并发的?

这个问题实际上并不适用。当所有基础任务WhenAll都完成时,任务就完成了。它如何做到这一点是它的业务。

当涉及到异常时,包含一个包含所有底层任务抛出的所有异常的Exception属性。TaskAggregateException

当您await的任务具有表示多个异常的聚合异常时,它将展开并重新抛出该列表中的第一个异常,而不是AggregateException其中的所有异常。

创建AggregateException它时(显然;我不知道这是否在任何地方都得到保证)根据传递给的任务的顺序列出异常WhenAll,而不是基于这些任务完成的顺序。

如果您担心丢失的异常,那么您应该存储它返回的任务,以便您可以检查所有异常,或者只是重新抛出 Wrapped AggregateException,即:

public async Task Hello()
{
    var j = await GetJasonAsync();
    var task = Task.WhenAll(new Task<string>[] { GetJasonAsync(), GetMikeAsync(), GetMitchAsync() });
    try
    {
        string[] dankeSchon = await task;
    }
    catch (Exception)
    {
        throw task.Exception;
    }
}

如果您真的希望首先被击中的异常是可以重新抛出的异常。一种选择是基本上重写WhenAll为我们自己的版本,只是处理异常的方式略有不同。另一种选择是根据任务完成的顺序对任务进行排序,有趣的是,我们可以这样做,同时仍然保持异步并且对任务一无所知。这是一个Order方法,它接受一系列任务并返回代表相同操作的任务序列,但根据完成时间排序(按升序排列)。

public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
    var taskList = tasks.ToList();

    var taskSources = new BlockingCollection<TaskCompletionSource<T>>();

    var taskSourceList = new List<TaskCompletionSource<T>>(taskList.Count);
    foreach (var task in taskList)
    {
        var newSource = new TaskCompletionSource<T>();
        taskSources.Add(newSource);
        taskSourceList.Add(newSource);

        task.ContinueWith(t =>
        {
            var source = taskSources.Take();

            if (t.IsCanceled)
                source.TrySetCanceled();
            else if (t.IsFaulted)
                source.TrySetException(t.Exception.InnerExceptions);
            else if (t.IsCompleted)
                source.TrySetResult(t.Result);
        }, CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);
    }

    return taskSourceList.Select(tcs => tcs.Task);
}

本质上,这里的想法是为每个任务创建一个TaskCompletionSource,为提供给我们的每个任务添加一个延续,然后当任何任务完成时,我们将尚未完成的 TaskCompletionSource 标记为刚刚完成任务的结果是。

使用它,我们现在可以编写:

public async Task Hello()
{
    var j = await GetJasonAsync();
    var tasks = new[] { GetJasonAsync(), GetMikeAsync(), GetMitchAsync() };
    string[] dankeSchon = await Task.WhenAll(tasks.Order());
}

并且异常将是首先抛出的异常。

于 2013-10-02T16:40:00.787 回答