我遇到一些麻烦让我的头脑异步/等待。我正在帮助使用具有以下代码的现有代码库(简化为了简洁起见):
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
}
如何使用async / await将此代码转换为异步代码而不是并行代码?我遇到了一些非常严重的性能问题,我认为这是使用并行方法进行多个网络I / O操作的结果。
我自己尝试了几种方法,但是我从Visual Studio收到警告,我的代码将同步执行,或者我不能在异步方法之外使用等待关键字,所以我确定我只是遗漏了一些东西简单。
编辑#1:我也愿意接受async / await的替代方案。到目前为止,这似乎是基于我阅读的正确方法。
编辑#2:此应用程序是Windows服务。它呼吁几个“买家”要求他们对特定数据进行投标。在处理继续之前,我需要回复所有出价。
答案 0 :(得分:3)
让事情变得异步的关键&#34;是从树叶开始。在这种情况下,从您的网络代码(未显示)开始,并将您拥有的任何同步呼叫(例如,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)
{
...
}
答案 1 :(得分: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();
如果你仍然需要能够对这些方法进行同步调用,我建议你创建一个以“Async”为后缀的新方法。
现在,您需要在每个级别执行此操作,直到您拨打网络电话,此时您将能够等待.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
}