2015-10-02 65 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) 
{ 
    ... 
} 
相关问题