2013-09-23 55 views
73

之間的區別是否有人可以解釋awaitContinueWith在下例中是否同義。我第一次嘗試使用TPL,並一直在閱讀所有文檔,但不明白其中的差異。等待和ContinueWith

等待

String webText = await getWebPage(uri); 
await parseData(webText); 

ContinueWith

Task<String> webText = new Task<String>(() => getWebPage(uri)); 
Task continue = webText.ContinueWith((task) => parseData(task.Result)); 
webText.Start(); 
continue.Wait(); 

是一個在特定情況下優於其他?

+2

如果您刪除在第二個例子中'Wait'通話*然後*這兩個片段會(大部分)等同。的 – Servy

+1

可能重複的[是異步等待關鍵字相當於ContinueWith拉姆達?](http://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda) –

+0

供參考:您的'getWebPage'方法不能在兩個代碼中使用。在第一個代碼中它有一個'Task '返回類型,而在第二個代碼中它有'string'返回類型。所以基本上你的代碼不能編譯。 - 如果要精確。 –

回答

61

在第二個代碼中,你同步等待繼續完成。在第一個版本中,只要方法遇到尚未完成的第一個表達式,該方法就會返回給調用方。

他們是在非常相似,它們都安排的延續,但只要控制流甚至略有變得複雜,await導致簡單的代碼。此外,正如Servy在評論中指出的那樣,等待任務將「解包」聚合異常,這通常會導致更簡單的錯誤處理。同樣使用await將隱式調度調用上下文中的延續(除非使用ConfigureAwait)。這不是「手動」無法完成的,但通過await可以輕鬆完成。

我建議你嘗試執行操作的稍大序列既awaitTask.ContinueWith - 它可以是一個真正的大開眼界。

+0

這兩個片段之間的錯誤處理也不同;在這方面,通過'ContinueWith'上的'await'通常更容易。 – Servy

+0

@Servy:真的,會增加一些東西。 –

+1

調度也是完全不同的,也就是'parseData'執行的上下文。 –

61

下面是我最近用來說明使用異步解決方案的差異和各種問題的代碼片段序列。

假設您在基於GUI的應用程序中有一些事件處理程序花費了大量時間,因此您希望使其處於異步。這裏是你開始的同步邏輯:

while (true) { 
    string result = LoadNextItem().Result; 
    if (result.Contains("target")) { 
     Counter.Value = result.Length; 
     break; 
    } 
} 

LoadNextItem返回一個Task,它最終會產生一些你想檢查的結果。如果當前結果是您正在查找的結果,則更新UI上某個計數器的值,然後從該方法返回。否則,您將繼續處理來自LoadNextItem的更多項目。

異步版本的第一個想法:只使用continuations!讓我們暫時忽略循環部分。我的意思是,什麼可能會出錯?

return LoadNextItem().ContinueWith(t => { 
    string result = t.Result; 
    if (result.Contains("target")) { 
     Counter.Value = result.Length; 
    } 
}); 

太棒了,現在我們有一種方法不會阻塞!它崩潰了。 UI控件的任何更新都應該在UI線程上發生,因此您需要對此進行說明。值得慶幸的是,有指定的延續應該如何安排的選項,並有一個默認的只是這一點:

return LoadNextItem().ContinueWith(t => { 
    string result = t.Result; 
    if (result.Contains("target")) { 
     Counter.Value = result.Length; 
    } 
}, 
TaskScheduler.FromCurrentSynchronizationContext()); 

太好了,現在我們有一個不死機的方法!它反而失敗了。繼續是他們自己的獨立任務,他們的地位與先前的任務沒有關係。因此,即使LoadNextItem發生故障,調用者也只會看到已成功完成的任務。好吧,那只是傳遞異常時,如果有一個:

return LoadNextItem().ContinueWith(t => { 
    if (t.Exception != null) { 
     throw t.Exception.InnerException; 
    } 
    string result = t.Result; 
    if (result.Contains("target")) { 
     Counter.Value = result.Length; 
    } 
}, 
TaskScheduler.FromCurrentSynchronizationContext()); 

太好了,現在該實際工作。對於單個項目。現在,那個循環如何?事實證明,解決方案相當於原來的同步版本的邏輯將是這個樣子:

Task AsyncLoop() { 
    return AsyncLoopTask().ContinueWith(t => 
     Counter.Value = t.Result, 
     TaskScheduler.FromCurrentSynchronizationContext()); 
} 
Task<int> AsyncLoopTask() { 
    var tcs = new TaskCompletionSource<int>(); 
    DoIteration(tcs); 
    return tcs.Task; 
} 
void DoIteration(TaskCompletionSource<int> tcs) { 
    LoadNextItem().ContinueWith(t => { 
     if (t.Exception != null) { 
      tcs.TrySetException(t.Exception.InnerException); 
     } else if (t.Result.Contains("target")) { 
      tcs.TrySetResult(t.Result.Length); 
     } else { 
      DoIteration(tcs); 
     }}); 
} 

或者,而不是所有上述情況,你可以使用異步做同樣的事情:

async Task AsyncLoop() { 
    while (true) { 
     string result = await LoadNextItem(); 
     if (result.Contains("target")) { 
      Counter.Value = result.Length; 
      break; 
     } 
    } 
} 

現在好多了,不是嗎?

+0

謝謝,真的很好的解釋 –

+0

這是一個很好的例子 –

+0

這仍然更新用戶界面。 http://pastebin.com/Y8NddKp5 – MonsterMMORPG