是WhenAll顺序还是并发?

Jay*_*cee 2 c# async-await

每次下面都运行Mike的异常被捕获.

是否所有顺序涉及每个任务之间的延续上下文或所有任务是否同时运行?如果它同时发生,为什么迈克的例外总是被抓住而不是米奇的.我拖延迈克,以便给米奇一个机会.如果它是顺序的,那使它并发?在进行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");
    }
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*rvy 7

是WhenAll顺序还是并发?

这个问题并不适用.对于任务WhenAll完成后,所有的基本任务已完成.如何做到这一点就是它的业务.

当涉及到的异常,Exception财产Task包含AggregateException所有所有的基本任务,抛出的异常.

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

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

如果您担心丢失的异常,那么您应该存储它返回的任务,以便您可以检查所有异常,或者只是重新抛出包装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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你真的想让首先被击中的异常是一个可以重新抛出的异常.一种选择是基本上重写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);
}
Run Code Online (Sandbox Code Playgroud)

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

使用这个我们现在可以写:

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

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