2015-10-02 64 views
2

我在使用異步/等待時遇到了一些麻煩。我與具有以下代碼(簡化爲簡潔起見)現有的代碼庫幫助:如何將此Parallel.ForEach代碼轉換爲異步/等待

List<BuyerContext> buyerContexts = GetBuyers(); 
var results = new List<Result>(); 

Parallel.ForEach(buyerContexts, buyerContext => 
{ 
    //The following call creates a connection to a remote web server that 
    //can take up to 15 seconds to respond 
    var result = Bid(buyerContext); 

    if (result != null) 
     results.Add(result); 
} 

foreach (var result in results) 
{ 
    // do some work here that is predicated on the 
    // Parallel.ForEach having completed all of its calls 
} 

我怎麼能這個代碼轉換爲異步代碼,而不是並行使用異步/等待?我受到一些非常嚴重的性能問題的困擾,我認爲這是使用並行方法處理多個網絡I/O操作的結果。

我已經嘗試了幾種方法,但我從Visual Studio獲得警告,我的代碼將同步執行,或者我無法使用await關鍵字以外的異步方法,所以我確信我只是缺少簡單一些。

編輯#1:我打開替代異步/等待以及。根據我的閱讀,這似乎是正確的方法。

編輯#2:此應用程序是一個Windows服務。它呼籲幾個「買家」要求他們競標一個特定的數據。在處理可以繼續之前,我需要全部出價。

+0

您可以添加出價方法定義嗎?如果它是網絡相關的,它將真正有助於查看網絡代碼:) –

+0

聽起來像是一個非常昂貴的呼叫在一個循環。你需要預先得到所有的結果嗎?您是否可以得到一些結果,並且只有在用戶要求更多時才返回更多? –

+0

一個簡單的解決方案見http://stackoverflow.com/questions/9290498/how-can-i-limit-parallel-foreach。 – beerboy

回答

2

基本上,利用async-await,該Bid方法應該有這樣的簽名,而不是目前的一個:

public async Task<Result> BidAsync(BuyerContext buyerContext); 

這將讓你在這個方法中使用await。現在,每當你打網絡電話時,你基本上都需要await它。例如,以下是如何將同步方法的調用和簽名修改爲異步方法。

以前

//Signature 
public string ReceiveStringFromClient(); 

//Call 
string messageFromClient = ReceiveStringFromClient(); 

//Signature 
public Task<string> ReceiveStringFromClientAsync(); 

//Call 
string messageFromClient = await ReceiveStringFromClientAsync(); 

如果您仍然需要能夠進行同步調用這些方法,我會建議創建具有「異步」後綴換新。

現在,您需要在每個級別都執行此操作,直到您到達網絡呼叫,此時您將能夠等待.Net的async方法。它們通常與其同步版本具有相同的名稱,後綴爲「Async」。

一旦你完成了所有這些,你可以在你的主代碼中使用它。我會沿着這些線做些事情:

List<BuyerContext> buyerContexts = GetBuyers(); 
var results = new List<Result>(); 

List<Task> tasks = new List<Task>(); 

//There really is no need for Parallel.ForEach unless you have hundreds of thousands of requests to make. 
//If that's the case, I hope you have a good network interface! 
foreach (var buyerContext in buyerContexts) 
{ 
    var task = Task.Run(async() => 
    { 
     var result = await BidAsync(buyerContext);   

     if (result != null) 
      results.Add(result); 
    }); 

    tasks.Add(task); 
} 

//Block the current thread until all the calls are completed 
Task.WaitAll(tasks); 

foreach (var result in results) 
{ 
    // do some work here that is predicated on the 
    // Parallel.ForEach having completed all of its calls 
} 
+0

我必須失去,你填充'tasks'部分... – canon

+0

這是真的,我從單行改爲可變+添加卻忘了第二個,固定! –

+0

謝謝!一個問題:異步方法要以Async()結束嗎?詢問的原因是Bid()方法包含對通過接口實現的方法的調用。如果我必須將方法名稱更改爲Async(),則必須更新大約50個項目。 :) – Scott

3

「使事情異步」的關鍵是從樹葉開始。在這種情況下,從您的網絡代碼(未顯示)開始,並將您擁有的任何同步呼叫(例如,WebClient.DownloadString)更改爲相應的異步呼叫(例如HttpClient.GetStringAsync)。然後await那個調用。

使用await將強制呼叫方法爲async,並將其返回類型從T更改爲Task<T>。在這一點上添加Async後綴也是一個好主意,因此您正在關注the well-known convention。然後採取所有的方法的調用者和改變他們使用await爲好,那麼這將要求它們async等重複,直到你有一個BidAsync方法來使用。

然後你應該看看免去您的並行循環;這很容易做到Task.WhenAll

List<BuyerContext> buyerContexts = GetBuyers(); 
var tasks = buyerContexts.Select(buyerContext => BidAsync(buyerContext)); 
var results = await Task.WhenAll(tasks); 

foreach (var result in results) 
{ 
    ... 
} 
相關問題