如何让C#异步方法实际并行运行?

时间:2015-03-17 23:17:51

标签: c# async-await

我之前从未使用async方法,但我发现自己构建了一个监视工具,并认为我可以使用这种方法来加快速度。

该工具定义了 SiteConfigurations 列表,每个 SiteConfiguration 都有一个待测试的监视器列表(通过HTTP请求)。该工具的体系结构基于管道,例如:

  • 当监视器成功时,执行MonitorSuccessPipeline
  • 当监视器失败时,执行MonitorFailurePipeline
  • 当站点配置中的所有监视器都已运行时,SiteAfterMonitorsPipeline将被执行
  • 当所有网站配置都已运行时,EndEnginePipeline将被执行

最初的方法是循环遍历所有SiteConfigurations,并为每个Monitors运行所有关联的RunMonitor

总执行时间:100个SiteConfigurations x 4监视器每个监视器x~1秒= ~400秒运行。

所以我将async更改为 private async Task<Result> RunMonitor(Monitor currentMonitor) { Result result = new Result(); var client = new HttpClient(); var response = client.GetAsync(currentMonitor.TestUrl); var content = await response.Result.Content.ReadAsStringAsync(); bool containSuccessText = content.Contains(currentMonitor.SuccessText); bool isOk = status == HttpStatusCode.OK; result.Success = containSuccessText && isOk; if (result.Success) { ExecutePipeline("MonitorSuccessPipeline", currentMonitor); } else { ExecutePipeline("MonitorFailurePipeline", currentMonitor); } return result; } (实际方法比您在此处看到的更多,这是&#34;核心&#34;方法):

RunSiteConfigurationMonitors

接下来,我将 private async Task<Result> RunSiteConfigurationMonitors(SiteConfiguration siteConfig) { Result result = new Result(); List<Task<Result>> tasks = new List<Task<Result>>(); foreach (var currentMonitor in siteConfig.Monitors) { tasks.Add(RunMonitor(siteConfig, globalConfig, currentMonitor)); } var results = await Task.WhenAll(tasks); foreach (var r in results) { result.Insert(r.Body); } ExecutePipeline("SiteAfterMonitorsPipeline", siteConfig); return result; } 方法更改为:

RunEngine

最后,我将 public async Task RunEngine(string configName) { GlobalConfiguration globalConfig = GetConfiguration(configName); List<Task<Result>> tasks = new List<Task<Result>>(); foreach (var sc in globalConfig.SiteConfigurations) { tasks.Add(RunSiteConfigurationMonitors(sc, globalConfig)); } var results = await Task.WhenAll(tasks); foreach (var r in results) { logger.Insert(r.Body); } ExecutePipeline("EndEnginePipeline", globalConfig); } 方法更改为:

RunEngine

我希望看到RunSiteConfigurationMonitors方法调用所有RunSiteConfigurationMonitors并看到它们同时并行运行 - 然后每个RunMonitor调用[21:56:50.419]: Loading configuration... [21:56:58.480]: [21:56:52.711]: Monitoring site A [21:56:58.583]: [21:56:53.954]: [21:56:52.753]: Found monitor: A1 [21:56:58.687]: [21:56:53.954]: [21:56:52.753]: Testing URL: http://a1.example.com [21:56:58.791]: [21:56:53.954]: [21:56:53.106]: -- Contains success text? Yes [21:56:58.894]: [21:56:53.954]: [21:56:53.106]: -- Status Code: OK [21:56:58.997]: [21:56:53.954]: [21:56:53.106]: -- Success? True [21:56:59.100]: [21:56:53.954]: [21:56:59.203]: [21:56:53.954]: [21:56:53.474]: Found monitor: A2 [21:56:59.306]: [21:56:53.954]: [21:56:53.474]: Testing URL: http://a2.example.com [21:56:59.375]: [21:56:53.954]: [21:56:53.761]: -- Contains success text? Yes [21:56:59.478]: [21:56:53.954]: [21:56:53.762]: -- Status Code: OK [21:56:59.582]: [21:56:53.954]: [21:56:53.762]: -- Success? True [21:56:59.686]: [21:56:53.954]: [21:56:59.790]: [21:56:59.894]: [21:56:54.126]: Monitoring site: B [21:56:59.998]: [21:56:56.424]: [21:56:54.126]: Found monitor: B1 [21:57:00.101]: [21:56:56.424]: [21:56:54.126]: Testing URL: http://b1.example.com [21:57:00.204]: [21:56:56.424]: [21:56:55.225]: -- Contains success text? Yes [21:57:00.307]: [21:56:56.424]: [21:56:55.225]: -- Status Code: OK [21:57:00.410]: [21:56:56.424]: [21:56:55.225]: -- Success? True [21:57:00.515]: [21:56:56.424]: [21:57:00.619]: [21:56:56.424]: [21:56:55.428]: Found monitor: B2 [21:57:00.724]: [21:56:56.424]: [21:56:55.429]: Testing URL: http://b2.example.com [21:57:00.827]: [21:56:56.424]: [21:56:56.254]: -- Contains success text? Yes [21:57:00.931]: [21:56:56.424]: [21:56:56.254]: -- Status Code: OK [21:57:01.036]: [21:56:56.424]: [21:56:56.254]: -- Success? True [21:57:01.140]: [21:56:56.424]: [21:57:01.244]: [21:57:01.348]: [21:56:56.597]: Monitoring site: C [21:57:01.452]: [21:56:58.206]: [21:56:56.597]: Found monitor: C1 [21:57:01.557]: [21:56:58.206]: [21:56:56.597]: Testing URL: http://c1.example.com [21:57:01.662]: [21:56:58.206]: [21:56:57.219]: -- Contains success text? Yes [21:57:01.766]: [21:56:58.206]: [21:56:57.219]: -- Status Code: OK [21:57:01.869]: [21:56:58.206]: [21:56:57.219]: -- Success? True [21:57:01.974]: [21:56:58.206]: [21:57:02.078]: [21:56:58.206]: [21:56:57.418]: Found monitor: C2 [21:57:02.182]: [21:56:58.206]: [21:56:57.418]: Testing URL: http://c2.example.com [21:57:02.287]: [21:56:58.206]: [21:56:58.025]: -- Contains success text? Yes [21:57:02.392]: [21:56:58.206]: [21:56:58.025]: -- Status Code: OK [21:57:02.496]: [21:56:58.206]: [21:56:58.025]: -- Success? True [21:57:02.602]: [21:56:58.206]: [21:57:02.706]: 和看那些同时运行。

相反,这就是监视器子集的输出:

RunMonitor

正如您所看到的,监视器基本上按顺序运行,并且站点配置也未在&#34; parallel&#34;中运行。

我的期望是错误的,还是我的代码有问题?就像我说的,这对我来说是个新世界,因此如果可能的话,我会真的欣赏一个简单的解释。

根据Stephen Cleary的建议,我将 private async Task<Result> RunMonitor(Monitor currentMonitor) { Result result = new Result(); var client = new HttpClient(); var response = await client.GetAsync(currentMonitor.TestUrl); var content = await response.Content.ReadAsStringAsync(); var status = response.StatusCode; bool containSuccessText = content.Contains(currentMonitor.SuccessText); bool isOk = status == HttpStatusCode.OK; result.Success = containSuccessText && isOk; if (result.Success) { ExecutePipeline("MonitorSuccessPipeline", currentMonitor); } else { ExecutePipeline("MonitorFailurePipeline", currentMonitor); } return result; } 更改为:

[23:33:44.199]: Loading configuration...
[23:33:48.968]: [23:33:46.601]: Monitoring site A
[23:33:49.073]: [23:33:47.637]: [23:33:46.625]: Found monitor: A1
[23:33:49.176]: [23:33:47.637]: [23:33:46.625]: Testing URL: http://a1.example.com
[23:33:49.281]: [23:33:47.637]: [23:33:47.047]: -- Contains success text? Yes
[23:33:49.386]: [23:33:47.637]: [23:33:47.047]: -- Status Code: OK
[23:33:49.490]: [23:33:47.637]: [23:33:47.047]: -- Success? True
[23:33:49.594]: [23:33:47.637]: 
[23:33:49.667]: [23:33:47.637]: [23:33:46.692]: Found monitor: A2
[23:33:49.770]: [23:33:47.637]: [23:33:46.692]: Testing URL: http://a2.example.com
[23:33:49.874]: [23:33:47.637]: [23:33:47.461]: -- Contains success text? Yes
[23:33:49.978]: [23:33:47.637]: [23:33:47.461]: -- Status Code: OK
[23:33:50.082]: [23:33:47.637]: [23:33:47.461]: -- Success? True
[23:33:50.186]: [23:33:47.637]: 
[23:33:50.292]: 
[23:33:50.396]: [23:33:46.727]: Monitoring site B
[23:33:50.500]: [23:33:48.690]: [23:33:46.727]: Found monitor: B1
[23:33:50.604]: [23:33:48.690]: [23:33:46.727]: Testing URL: http://b1.example.com
[23:33:50.708]: [23:33:48.690]: [23:33:48.547]: -- Contains success text? Yes
[23:33:50.812]: [23:33:48.690]: [23:33:48.547]: -- Status Code: OK
[23:33:50.915]: [23:33:48.690]: [23:33:48.547]: -- Success? True
[23:33:51.019]: [23:33:48.690]: 
[23:33:51.124]: [23:33:48.690]: [23:33:46.727]: Found monitor: B2
[23:33:51.228]: [23:33:48.690]: [23:33:46.727]: Testing URL: http://b2.example.com
[23:33:51.332]: [23:33:48.690]: [23:33:48.336]: -- Contains success text? Yes
[23:33:51.437]: [23:33:48.690]: [23:33:48.336]: -- Status Code: OK
[23:33:51.541]: [23:33:48.690]: [23:33:48.336]: -- Success? True
[23:33:51.645]: [23:33:48.690]: 
[23:33:51.749]: 
[23:33:51.852]: [23:33:46.728]: Monitoring site C
[23:33:51.956]: [23:33:48.161]: [23:33:46.728]: Found monitor: C1
[23:33:52.060]: [23:33:48.161]: [23:33:46.728]: Testing URL: http://c1.example.com
[23:33:52.165]: [23:33:48.161]: [23:33:47.813]: -- Contains success text? Yes
[23:33:52.271]: [23:33:48.161]: [23:33:47.813]: -- Status Code: OK
[23:33:52.375]: [23:33:48.161]: [23:33:47.813]: -- Success? True
[23:33:52.479]: [23:33:48.161]: 
[23:33:52.583]: [23:33:48.161]: [23:33:46.760]: Found monitor: C2
[23:33:52.688]: [23:33:48.161]: [23:33:46.760]: Testing URL: http://c2.example.com
[23:33:52.793]: [23:33:48.161]: [23:33:47.987]: -- Contains success text? Yes
[23:33:52.897]: [23:33:48.161]: [23:33:47.987]: -- Status Code: OK
[23:33:53.001]: [23:33:48.161]: [23:33:47.987]: -- Success? True
[23:33:53.105]: [23:33:48.161]: 
[23:33:53.208]: 

现在它正在并行工作!

{{1}}
但是,我必须承认,我并不真正理解为什么这有效。

2 个答案:

答案 0 :(得分:8)

异步编程的一个指导原则是不阻止异步代码。请考虑RunMonitor中的此代码:

var response = client.GetAsync(currentMonitor.TestUrl);
var content = await response.Result.Content.ReadAsStringAsync();

response的类型是一项任务,下一行访问Task<T>.Result同步阻止该方法,直到HTTP请求完成。所以这个方法不是异步操作的。你可能想要的是:

var response = await client.GetAsync(currentMonitor.TestUrl);
var content = await response.Content.ReadAsStringAsync();

答案 1 :(得分:1)

评论通常是正确的。任务的调度取决于运行时,无论是Web应用程序,控制台应用程序等。或者,如果您的应用程序只是一个按计划执行的监视工具,您可以使用Parallel.ForEach()方法集您的RunEngine()RunSiteConfigurationMonitors而不是等待任务。您的每个RunMonitor都会将结果写入ConcurrentBag<>。使用Parallel.ForEach,您还可以通过ParallelOptions控制并行度,但同样,也不会超过调度程序提供的线程数。希望这会有所帮助。