2014-09-20 77 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