2017-09-15 41 views
2

我测试C#异步的asynchronousity /等待和跨越一个惊喜,其中用于ContinueWith随后的代码不等待前面的任务完成就来了:C#链式ContinueWith不是在等待前一个任务来完成

public async Task<int> SampleAsyncMethodAsync(int number,string id) 
    { 
      Console.WriteLine($"Started work for {id}.{number}"); 
      ConcurrentBag<int> abc = new ConcurrentBag<int>(); 

      await Task.Run(() => { for (int count = 0; count < 30; count++) { Console.WriteLine($"[{id}] Run: {number}"); abc.Add(count); } }); 

      Console.WriteLine($"Completed work for {id}.{number}"); 
      return abc.Sum(); 
     } 

 [Test] 
     public void TestAsyncWaitForPreviousTask() 
     { 
      for (int count = 0; count < 3; count++) 
      { 
       int scopeCount = count; 

       var c = SampleAsyncMethodAsync(0, scopeCount.ToString()) 
       .ContinueWith((prevTask) => 
       { 
        return SampleAsyncMethodAsync(1, scopeCount.ToString()); 
       }) 
       .ContinueWith((prevTask2) => 
       { 
        return SampleAsyncMethodAsync(2, scopeCount.ToString()); 
       }); 
      } 
     } 

输出显示运行执行0.0,1.0和2.0正确异步执行,但随后的X.1和十,得到几乎立即开始和十,实际完成:这是与下面的测试方法执行

在x.1之前。例如。如下记录:

[2] Run: 0 
[2] Run: 0 
[2] Run: 0 
Completed work for 2.0 
Started work for 0.1 
Started work for 0.2 <-- surprise! 
[0] Run: 2 
[0] Run: 2 
[0] Run: 2 
[0] Run: 2 
[0] Run: 2 

看来continueWith仅将在第一个任务(0)等,无论后续的链条。 我可以通过在第一个Continuewith块中嵌套第二个ContinueWith来解决问题。

我的代码有问题吗?我假设Console.WriteLine尊重FIFO。

+0

['ContinueWithWith'是一种极其低级的管道方法](http://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html)。你应该使用'await'而不是'ContinueWith'。 –

回答

3

总之,您期望ContinueWith等待先前返回的对象。返回一个对象(即使是TaskContinueWith动作对返回值没有任何作用,它不会等待它完成,它会返回它并在存在时传递给继续。

以后的事确实发生了:

  • 您运行SampleAsyncMethodAsync(0, scopeCount.ToString())
  • 当它完成后,您执行的延续1:

    return SampleAsyncMethodAsync(1, scopeCount.ToString()); 
    

    ,当它绊倒在await Task.Run,它返回一个任务。即,它不等待SampleAsyncMethodAsync完成。

  • 然后,继续1被认为是完成的,因为它已经返回一个值(任务)
  • 继续2被运行。

如果手动等待每异步方法,那么它会因此运行:

for (int count = 0; count < 3; count++) 
{ 
    int scopeCount = count; 

    var c = SampleAsyncMethodAsync(0, scopeCount.ToString()) 
    .ContinueWith((prevTask) => 
    { 
     SampleAsyncMethodAsync(1, scopeCount.ToString()).Wait(); 
    }) 
    .ContinueWith((prevTask2) => 
    { 
     SampleAsyncMethodAsync(2, scopeCount.ToString()).Wait(); 
    }); 
}  

使用ContinueWith(async t => await SampleAsyncMethodAsync...不工作为好,因为它会导致成包裹Task<Task>结果(很好地解释here)。

此外,你可以这样做:

for (int count = 0; count < 3; count++) 
{ 
    int scopeCount = count; 

    var c = SampleAsyncMethodAsync(0, scopeCount.ToString()) 
     .ContinueWith((prevTask) => 
     { 
      SampleAsyncMethodAsync(1, scopeCount.ToString()) 
       .ContinueWith((prevTask2) => 
       { 
        SampleAsyncMethodAsync(2, scopeCount.ToString()); 
       }); 
     }); 
} 

但是,它会创建某种回调地狱和看起来杂乱无章。

您可以使用await使这个代码干净了一点:

for (int count = 0; count < 3; count++) 
{ 
    int scopeCount = count; 

    var d = Task.Run(async() => { 
     await SampleAsyncMethodAsync(0, scopeCount.ToString()); 
     await SampleAsyncMethodAsync(1, scopeCount.ToString()); 
     await SampleAsyncMethodAsync(2, scopeCount.ToString()); 
    }); 
} 

现在,它运行3个计数3个任务,每个任务都会因此与运行异步方法number等于1,2, 3。

+0

是的,ContinueWith的行为确实确认行为,但是MSDN声明该方法返回一个新的延续任务。 https://msdn.microsoft.com/en-us/library/dd270696(v=vs.110).aspx – TropicalFruitz

+0

@TropicalFruitz好的,现在我明白你的意思了,你说得对。 'ContinueWith'确实返回一个全新的任务。它看起来好像与使用'async'有关。使'SampleAsyncMethodAsync'不是异步会让你的代码运行正确。非常有趣,如果我理解或删除它,我会更新我的答案。 –

+0

@TropicalFruitz请你看看最新的答案。我无法解释它:( –