2014-09-20 62 views
-1

我有很长时间的运行处理,我想在后台任务中执行。在任务结束时,我想表示它已完成。所以基本上我有两个异步任务,我想在后台运行一个接一个。序列化异步任务和异步继续

我在继续执行此操作,但是继续操作是在完成初始任务之前开始的。预期的行为是继续只在完成初始任务后运行。

下面是一个说明该问题的一些示例代码:

// Setup task and continuation 
var task = new Task(async() => 
{ 
    DebugLog("TASK Starting"); 
    await Task.Delay(1000);  // actual work here 
    DebugLog("TASK Finishing"); 
}); 

task.ContinueWith(async (x) => 
{ 
    DebugLog("CONTINUATION Starting"); 
    await Task.Delay(100);  // actual work here 
    DebugLog("CONTINUATION Ending"); 
}); 

task.Start(); 

的DEBUGLOG功能:

static void DebugLog(string s, params object[] args) 
{ 
    string tmp = string.Format(s, args); 
    System.Diagnostics.Debug.WriteLine("{0}: {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), tmp); 
} 

预期输出:

TASK Starting 
TASK Finishing 
CONTINUATION Starting 
CONTINUATION Ending 

实际输出:

TASK Starting 
CONTINUATION Starting 
CONTINUATION Ending 
TASK Finishing 

同样,我的问题是为什么在完成初始任务之前继续开始?如何在第一项任务完成后继续运行?


变通办法#1

我可以让上面的代码工作,如果我做INTIAL任务同步如预期 - 那就是如果我WaitTask.Delay像这样:    

var task = new Task(() => 
{ 
    DebugLog("TASK Starting"); 
    Task.Delay(1000).Wait();  // Wait instead of await 
    DebugLog("TASK Finishing"); 
}); 

由于很多原因,使用Wait这样做很不好。一个原因是它阻塞了线程,这是我想避免的。


变通办法#2

如果我接任务的创建,并将其移动到它自己的功能,这似乎也工作:

// START task and setup continuation 
var task = Test1(); 
task.ContinueWith(async (x) => 
{ 
    DebugLog("CONTINUATION Starting"); 
    await Task.Delay(100);  // actual work here 
    DebugLog("CONTINUATION Ending"); 
}); 

static public async Task Test1() 
{ 
    DebugLog("TASK Starting"); 
    await Task.Delay(1000);  // actual work here 
    DebugLog("TASK Finishing"); 
} 

信贷的上述方法去到这个有点相关(但不重复)的问题:Use an async callback with Task.ContinueWith


解决方法#2比解决方法#1更好,如果没有解释为什么我上面的初始代码不起作用,我可能会采取这种方法。

回答

4

由于async lambda被翻译为async void下方的方法,它没有内置的方法来通知任何已完成的代码,因此您的原始代码不起作用。所以,你所看到的是async voidasync Task之间的差异。这是你应该避免的原因之一async void。也就是说,如果可以使用你的代码,使用Task.Run而不是Task的构造函数和Start;更自然

var task = Task.Run(async() => 
{ 
    DebugLog("TASK Starting"); 
    await Task.Delay(1000);  // actual work here 
    DebugLog("TASK Finishing"); 
}); 
await task; 
DebugLog("CONTINUATION Starting"); 
await Task.Delay(100);  // actual work here 
DebugLog("CONTINUATION Ending"); 

Task.Runawait工作,async代码:和使用await而非ContinueWith

+0

-1:原始代码不工作,因为它在必要时不使用'Unwrap()'方法。创建一个'async void' lambda当然不是所希望的,但也只是没有使用'新任务(...)'的副作用,所以'Unwrap()'会起作用。 – 2014-09-20 19:56:37

+0

@ 280Z28:是的,原始代码可能会被黑客使用'新的任务'和'Unwrap';然而,新的代码会工作*,因为*它会避免一个'异步void'lambda。它仍然不如基于Task.Run的解决方案。 – 2014-09-20 19:59:12

+0

+1我结束了使用'Task.Run'和原来的延续。感谢您深入了解为什么我现有的代码不起作用:lambda变成'async void'。当然,当你这样说时,一切都变得更有意义。那么,为什么我要使用延续?那么,这里试图解释太多了,尽管它与[这个问题]有很大关系(http://stackoverflow.com/questions/11878654/winrt-loading-data-while-keeping-the-ui-响应)。 – 2014-09-22 01:32:36

4

您不应该直接使用Task构造函数来启动任务,特别是在启动异步任务时。如果你想卸载工作,在后台使用Task.Run代为执行:

var task = Task.Run(async() => 
{ 
    DebugLog("TASK Starting"); 
    await Task.Delay(1000);  // actual work here 
    DebugLog("TASK Finishing"); 
}); 

关于延续,这将是最好只追加它的逻辑lambda表达式的结束。但是,如果你使用ContinueWith坚定你需要使用Unwrap获得实际的异步任务和储存,所以你可以处理异常:

task = task.ContinueWith(async (x) => 
{ 
    DebugLog("CONTINUATION Starting"); 
    await Task.Delay(100);  // actual work here 
    DebugLog("CONTINUATION Ending"); 
}).Unwrap(); 

try 
{ 
    await task; 
} 
catch 
{ 
    // handle exceptions 
} 
+0

+1谢谢你,'Task.Run'就是我需要的。我从来没有考虑过它,因为我看到[像这样]的MSDN延续示例(http://msdn.microsoft.com/en-us/library/dd537612(v = vs.110).aspx)使用'new Task' 。我接受Stephen的回答是因为他解释了*为什么我的代码不起作用:我的初始任务lambda变成了'async void'而不是'async Task'。那么,为什么我要使用延续?那么,这里试图解释太多了,尽管它与[这个问题]有很大关系(http://stackoverflow.com/questions/11878654/winrt-loading-data-while-keeping-the-ui-响应)。 – 2014-09-22 01:24:19

0

更改您的代码

 // Setup task and continuation 
     var t1 = new Task(() => 
     { 
      Console.WriteLine("TASK Starting"); 
      Task.Delay(1000).Wait();  // actual work here 
      Console.WriteLine("TASK Finishing"); 
     }); 

     var t2 = t1.ContinueWith((x) => 
     { 
      Console.WriteLine("C1"); 
      Task.Delay(100).Wait();  // actual work here 
      Console.WriteLine("C2"); 
     }); 

     t1.Start(); 

     // Exception will be swallow without the line below 
     await Task.WhenAll(t1, t2); 
+0

我通常会尽量避免使用Wait,因为它会阻塞正在执行的线程,并且可能会导致线程死锁。有关死锁的更多信息,请参阅[本文](http://stackoverflow.com/questions/15021304/an-async-await-example-that-c​​auses-a-deadlock)。这篇文章使用'Result'而不是'Wait',但是它们都阻塞正在执行的线程,并且都可能导致死锁。 – 2014-12-18 14:37:41