立即等待异步任务与先声明然后等待

时间:2019-08-01 13:24:34

标签: c# asynchronous async-await task

让我们看一下以下两个示例:

public class MyClass 
{
    public async Task Main() 
    {
        var result1 = "";
        var result2 = "";

        var request1 = await DelayMe();
        var request2 = await DelayMe();

        result1 = request1;
        result2 = request2;        
    }

    private static async Task<String> DelayMe()
    {
        await Task.Delay(2000);
        return "";
    }
}

并且:

public class MyClass 
{
    public async Task Main() 
    {
        var result1 = "";
        var result2 = "";

        var request1 = DelayMe();
        var request2 = DelayMe();

        result1 = await request1;
        result2 = await request2;        
    }

    private static async Task<String> DelayMe()
    {
        await Task.Delay(2000);
        return "";
    }
}

在第一个示例中,您通常会编写async await代码,其中一件事情发生在另一件事情之后并正确等待。

第二个方法是首先调用async Task方法,但稍后会await对其进行调用。

第一个示例花费了4000ms一点点时间来执行,因为await在计算第二个请求之前正在计算第一个请求;但是第二个示例花费了2000ms一点点。发生这种情况是因为Task实际上是在执行跨过var request1 = DelayMe();行后立即开始运行的,这意味着request1request2是并行运行的。此时,看来await关键字只是确保了Task的计算。

第二种方法的感觉和作用类似于await Task.WhenAll(request1, request2),但是在这种情况下,如果2个请求中的某些操作失败,您将立即获得异常,而不是等待所有计算然后得到{{1 }}。

我的问题是,当第二种方法的结果不取决于执行时,使用第二种方法并行运行多个AggregateExceptionawait是否有缺点(性能)?其他的?查看降低的代码,看起来第二个示例生成了相等数量的Task 1 System.Threading.Tasks.Task async await状态机流?

2 个答案:

答案 0 :(得分:3)

  

如果2个请求中的某些操作失败,您将立即获得一个异常,而不是等待所有计算之后再获得AggregateException。

如果在 first 请求中某项失败,则为是。如果 second 请求失败,则不会,直到await完成该任务后,您才可以检查第二个请求的结果。

  

我的问题是,当一种方法的结果不依赖于另一种方法的执行时,使用第二种方法并行运行多个可等待任务是否有缺点(性能或其他方面)?查看降低的代码,看起来第二个示例生成了相等数量的System.Threading.Tasks.Task1per等待项目,而第一个则没有。这还在通过异步等待状态机流吗?

它仍在通过状态机流程。我倾向于推荐await Task.WhenAll,因为代码的意图更加明确,但是有些人不喜欢“即使有异常也总是等待”的行为。另一面是Task.WhenAll始终收集所有异常-如果您具有快速失败的行为,则可以忽略某些异常。

关于性能,并发执行会更好,因为您可以并发执行多个操作。因为async / await不会使用其他线程,所以没有线程池耗尽的危险。

作为一个旁注,我建议对此使用“异步并发”而不是“并行”,因为对于许多人来说,“并行”意味着并行处理,即Parallel或PLINQ,这将是在这种情况下使用错误的技术。

答案 1 :(得分:-1)

使用第二种方法并行运行多个等待的任务的缺点是并行性不明显。而且明显的并行性(换句话说是隐式多线程)并不危险,因为众所周知,可能引入的错误是不一致的,并且偶尔会被观察到。让我们假设在生产环境中运行的实际DelayMe是唯一的波纹管:

private static int delaysCount = 0;

private static async Task<String> DelayMe()
{
    await Task.Delay(2000);
    return (++delaysCount).ToString();
}

依次等待的对DelayMe的呼叫将返回递增的数字。并行等待的呼叫偶尔会返回相同的号码。