2015-08-22 203 views
13

虽然试图找出新的(可能不是现在这么新,但对我来说是新手)Task在C#中的异步编程,我遇到了一个问题,让我花了一些时间弄清楚,我不知道为什么。Task.WaitAll不等待任务完成

我已经解决了这个问题,但我仍然不确定为什么它是一个问题开始。我只是想我会分享我的经验,以防万一有人遇到同样的情况。

如果任何大师想告诉我问题的原因,这将是美好的,非常感激。我总是喜欢只知道为什么东西不起作用!

我做了一个测试任务,如下所示:

Random rng = new Random((int)DateTime.UtcNow.Ticks); 
int delay = rng.Next(1500, 15000); 
Task<Task<object>> testTask = Task.Factory.StartNew<Task<object>>(
    async (obj) => 
     { 
      DateTime startTime = DateTime.Now; 
      Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj); 
      await Task.Delay((int)obj); 
      Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); 
      return obj; 
     }, 
     delay 
    ); 
Task<Task<object>>[] tasks = new Task<Task<object>>[] { testTask }; 

Task.WaitAll(tasks); 
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); 

// make console stay open till user presses enter 
Console.ReadLine(); 

然后我跑的应用程序,看看它吐出来。以下是一些示例输出:

6:06:15.5661 - 延迟3053ms开始测试任务。
6:06:15.5662 - 已完成等待。
6:06:18.5743 - 3063.235ms后完成测试任务。

正如你所看到的,Task.WaitAll(tasks);声明并没有太多的作用。在继续执行之前,它总共等待1毫秒。

我已经在下面回答了我自己的“问题” - 但正如我上面所说的 - 如果有人比我更懂得解释为什么这不起作用,请做! (I 认为它可能与该方法的执行“跳出”有关,一旦它达到了await操作员 - 然后一旦等待完成后又退回......但我可能是错的)

+0

为什么要那样做'新的随机((int)的DateTime.UtcNow.Ticks)'?为什么不只是'新的Random()',因为它实际上是同一件事情。 – Enigmativity

+0

不是说这个问题对于这个问题是重要的或者相关的,而是纯粹为了看看它做了什么,如果有的话。我没有注意到与调用无参数构造函数有什么不同,所以我从现在开始就这样做。 如果你不尝试新的东西,你不会学到任何东西。编程对我来说是一种爱好,除了Karel Robot,Pascal和SQL之外,我还没有接受过任何正式的教育,而这已经是13年以前的事了,所以对我来说修改,尝试,破坏事物,图他们出去学习。 – cjk84

+0

你最好下载一个免费的.NET反编译器并查看源代码。 – Enigmativity

回答

17

您应该避免使用Task.Factory.StartNew与异步的await。您应该使用Task.Run

异步方法返回一个Task<T>,异步委托也可以。 Task.Factory.StartNew也返回一个Task<T>,其结果是委托参数的结果。所以当它们一起使用时,它会返回一个Task<Task<T>>>

这个Task<Task<T>>所做的就是执行委托,直到有一个任务返回,即第一个等待达到的时候。如果你只等待完成这个任务,你不会等待整个方法,只是第一个等待之前的部分。

您可以修复使用Task.Unwrap它创建了一个Task<T>,表示Task<Task<T>>>

Task<Task> wrapperTask = Task.Factory.StartNew(...); 
Task actualTask = wrapperTask.Unwrap(); 
Task.WaitAll(actualTask); 
+2

谢谢你的解释,这是有道理的。我已将此标记为正确答案! – cjk84

+1

@ cjk84肯定..任何时候。 – i3arnon

+1

对您来说还有一个问题:我在猜测是因为我删除了异步/等待并因此任务从任务>转到任务,它纯粹是因为这个原因,并且从Task.Delay更改为Thread。睡眠与它无关。是对的吗? – cjk84

0

经过多次调整和拉毛后,我终于决定摆脱异步lambda,并使用System.Threading.Thread.Sleep方法,看看是否有任何区别。

新的代码弄成这个样子:

Random rng = new Random((int)DateTime.UtcNow.Ticks); 
int delay = rng.Next(1500, 15000); 
Task<object> testTask = Task.Factory.StartNew<object>(
    (obj) => 
     { 
      DateTime startTime = DateTime.Now; 
      Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj); 
      System.Threading.Thread.Sleep((int)obj); 
      Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); 
      return obj; 
     }, 
     delay 
    ); 
Task<object>[] tasks = new Task<object>[] { testTask }; 

Task.WaitAll(tasks); 
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); 

// make console stay open till user presses enter 
Console.ReadLine(); 

注:由于去除拉姆达方法async关键字,任务类型就可以方便Task<object>而非Task<Task<object>> - 你可以看到这种变化上面的代码。

而且,瞧!有效!我得到了'已完成的等待'。任务完成后的消息。

唉,我记得在你的Task代码中读到的地方不应该使用System.Threading.Thread.Sleep()。不记得为什么;但因为它只是用于测试,而大多数任务实际上会做,而不是假装要做的事情需要时间,这应该不是问题。

希望这可以帮助一些人。我绝对不是世界上最好的程序员(甚至关闭),我的代码可能不是很好,但是如果它对别人有帮助,太棒了! :)

感谢您的阅读。

编辑:我的问题的其他答案解释为什么我有问题,我做了这个答案只解决了错误的问题。改为Thread.Sleep(x)没有效果。谢谢所有回答并帮助我的人!

3

这里,Task.WaitAll等待外部任务而不是内部任务。使用Task.Run没有嵌套的任务。这是最佳实践解决方案。另一个解决方案是等待内在的任务。例如:

Task<object> testTask = Task.Factory.StartNew(
    async (obj) => 
     { 
      ... 
     } 
    ).Unwrap(); 

或者:

testTask.Wait(); 
testTask.Result.Wait(); 
+0

啊,我想知道为什么它不会让我添加async关键字,直到我嵌套任务。谢谢你的信息! – cjk84

6

的问题与您的代码是有在作怪两个任务。一个是你的Task.Factory.StartNew调用的结果,它导致匿名函数在线程池上执行。但是,您的匿名函数又被编译为产生一个嵌套的任务,表示完成其异步操作。当您在Task<Task<object>>上等待时,您只需等待外部任务。要等在内的任务,你应该使用Task.Run代替Task.Factory.StartNew,因为它会自动解开你内心的任务:

Random rng = new Random((int)DateTime.UtcNow.Ticks); 
int delay = rng.Next(1500, 15000); 
Task<int> testTask = Task.Run(
    async() => 
    { 
     DateTime startTime = DateTime.Now; 
     Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), delay); 
     await Task.Delay(delay); 
     Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); 
     return delay; 
    }); 
Task<int>[] tasks = new[] { testTask }; 

Task.WaitAll(tasks); 
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); 

// make console stay open till user presses enter 
Console.ReadLine(); 
+0

谢谢你的回答! – cjk84