了解async / await vs在C#中使用“ContinueWith”行为等待

时间:2017-04-21 12:23:28

标签: c# .net multithreading asynchronous

一种方法是标准的异步方法,如下所示:

private static async Task AutoRetryHandlerAsync_Worker(Func<Task<bool>> taskToRun,...)

我测试了两个实现,一个使用等待,另一个使用 .Wait()

这两个实现完全不相同,因为相同的测试失败了await版本而不是Wait()版本。

此方法的目标是“执行输入函数返回的任务,并通过执行相同的函数重试直到它工作”(如果达到一定次数的尝试,则自动停止的限制)。

这有效:

private static async Task AutoRetryHandlerAsync_Worker(Func<Task<bool>> taskToRun,...)
{
    try {
       await taskToRun();
    }
    catch(Exception) 
   {
       // Execute later, and wait the result to complete
       await Task.Delay(currentDelayMs).ContinueWith(t =>
       {
            // Wait for the recursive call to complete
            AutoRetryHandlerAsync_Worker(taskToRun).Wait();
       });

       // Stop
       return;
    }    
}

这个(使用async t =>和使用await代替t =>以及使用.Wait()根本不起作用,因为递归调用的结果在最终return;执行之前不等待:

private static async Task AutoRetryHandlerAsync_Worker(Func<Task<bool>> taskToRun,...)
{
    try {
       await taskToRun();
    }
    catch(Exception) 
   {
       // Execute later, and wait the result to complete
       await Task.Delay(currentDelayMs).ContinueWith(async t =>
       {
            // Wait for the recursive call to complete
            await AutoRetryHandlerAsync_Worker(taskToRun);
       });

       // Stop
       return;
    }    
}

我试图理解为什么这个简单的改变会改变一切,当它应该做同样的事情时:等待ContinueWith完成。

如果我提取由ContinueWith方法运行的任务,我确实看到ContinueWith函数的状态在内部等待返回完成之前传递给“ranToCompletion”

为什么呢?是不是应该等待它?

具体的可测试行为

public static void Main(string[] args)
{
    long o = 0;
    Task.Run(async () =>
    {
        // #1 await
        await Task.Delay(1000).ContinueWith(async t =>
        {
            // #2 await
            await Task.Delay(1000).ContinueWith(t2 => {
                o = 10;
            });
        });
        var hello = o;
    });


    Task.Delay(10000).Wait();
}

为什么在o = 10之前达到var hello = o;

#1等待是否应该在执行继续之前挂起?

1 个答案:

答案 0 :(得分:6)

lambda语法模糊了ContinueWith( async void ...)的事实。

不等待异步void方法,并且它们抛出的任何错误都将被忽略。

对于你的基本问题,无论如何都不是在捕获内重试。太多了,抓住块应该很简单。并且直截了当地重试所有异常类型也是非常可疑的。你应该知道哪些错误可以保证重试,其余的通过。

简单易懂:

while (count++ < N)
{
   try
   {          
      MainAction();
      break;      
   }
   catch(MoreSpecificException ex) { /* Log or Ignore */ }

   Delay();
}