简体   繁体   English

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

[英]How to get C# async methods to actually run in parallel?

I have never used async methods before, but I found myself building a monitoring tool and thought that I could use this type of methods to speed things up. 我以前从未使用过async方法,但是我发现自己正在构建监视工具,并认为我可以使用这种类型的方法来加快处理速度。

The tool defines a list of SiteConfigurations , and each SiteConfiguration has a list of Monitors to be tested (via HTTP requests). 该工具定义一个SiteConfigurations列表,每个SiteConfiguration都有一个要测试的监视器列表(通过HTTP请求)。 The architecture of the tool is pipeline based, so for example: 该工具的体系结构基于管道,因此例如:

  • when a monitor succeeds, the MonitorSuccessPipeline is executed 监视成功时,将执行MonitorSuccessPipeline
  • when a monitor fails, the MonitorFailurePipeline is executed 当监视器发生故障时,将执行MonitorFailurePipeline
  • when all monitors within a site configuration have run, the SiteAfterMonitorsPipeline is executed 站点配置中的所有监视器都运行后,将执行SiteAfterMonitorsPipeline
  • when all site configurations have run, the EndEnginePipeline is executed 运行所有站点配置后,将执行EndEnginePipeline

The initial approach was to loop through all SiteConfigurations and, for each of them, run all associated Monitors . 最初的方法是遍历所有SiteConfigurations并针对每个SiteConfigurations运行所有关联的Monitors

Total execution time: 100 SiteConfigurations x 4 Monitors x ~1 second per monitor = ~400 seconds to run. 总执行时间:100个SiteConfigurations x 4个监视器x每个监视器约1秒=运行约400秒。

So I changed my RunMonitor to be async (the actual method does more than what you see here, this is the "core" of the method): 因此,我将RunMonitor更改为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;
    }

Next, I have changed the RunSiteConfigurationMonitors method to be: 接下来,我将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;
    }

Finally, I changed the RunEngine method to be: 最后,我将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);
    }

I expected to see the RunEngine method call all the RunSiteConfigurationMonitors and see them run in parallel at the same time - and in turn each RunSiteConfigurationMonitors call the RunMonitor and see those run at the same time. 我希望看到RunEngine方法调用所有RunSiteConfigurationMonitors并看到它们同时并行运行-然后每个RunSiteConfigurationMonitors调用RunMonitor并看到它们同时运行。

Instead, this is what was the output on a subset of monitors: 相反,这是监视器子集的输出:

[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]: 

As you can see, the monitors were essentially run in sequential order, and the site configurations also were not run in "parallel". 如您所见,监视器本质上是按顺序运行的,并且站点配置也不是“并行”运行的。

Were my expectations wrong to begin with, or is my code faulty? 一开始我的期望是错误的,还是我的代码有错误? Like I said, this is a new world for me, therefore I would really appreciate a simple explanation, if possible. 就像我说的,这对我来说是一个新的世界,所以我真的很感激一个简单的说明,如果可能的话。

SOLUTION

Following Stephen Cleary's suggestion below, I changed the RunMonitor to be: 按照Stephen Cleary的以下建议,我将RunMonitor更改为:

    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;
    }

and now it's working in parallel! 现在它正在并行工作!

[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]: 

I have to admit, though, that I didn't really understand why this worked. 不过,我不得不承认,我并不真正理解为什么这样做。

One of the guidelines for asynchronous programming is to not block on asynchronous code. 异步编程的准则之一是不要阻塞异步代码。 Consider this code in RunMonitor : RunMonitor考虑以下代码:

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

The type of response is a task, and the next line accesses Task<T>.Result , which synchronously blocks the method until the HTTP request is complete. response的类型是一个任务,下一行访问Task<T>.Result ,它会同步阻止该方法,直到HTTP请求完成。 So this method is not operating asynchronously. 因此,此方法不是异步操作。 What you probably want is: 您可能想要的是:

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

The comments are generally correct. 这些评论通常是正确的。 The scheduling of the tasks depends on the runtime, whether it is a web application, console application etc. Alternatively, if your application is just a monitoring tool that gets executed per a schedule, you could use Parallel.ForEach() set of methods in your RunEngine() and RunSiteConfigurationMonitors instead of awaiting tasks. 任务的调度取决于运行时,是否是Web应用程序,控制台应用程序等。或者,如果您的应用程序只是按调度执行的监视工具,则可以在其中使用Parallel.ForEach()方法集您的RunEngine()RunSiteConfigurationMonitors而不是等待任务。 Each of your RunMonitor would be writing results to a ConcurrentBag<> . 您的每个RunMonitor都将结果写入ConcurrentBag<> With Parallel.ForEach you could also control the degree of parallelism via ParallelOptions , but, again, never more that the number of threads provided by the scheduler. 使用Parallel.ForEach您还可以通过ParallelOptions控制并行度,但是再一次,它的数量绝不能超过调度程序提供的线程数。 Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM