简体   繁体   English

运行多个异步任务-更快的结果链接与并行?

[英]Running Multiple Async Tasks - Faster results Chaining vs Parallel?

I have 7 different API calls that I want to make at the same time, and want the UI to be updated when each has been completed. 我想同时进行7个不同的API调用,并且希望每个完成后都可以更新UI。

I have been fiddling with two different ways of doing this, chaining the requests, and shooting all requests off at the same time (in parallel). 我一直在摆弄两种不同的方法,将请求链接起来,并同时(并行)关闭所有请求。

Both seem to work but for some reason my Parallel tasks take significantly longer than when I chain them. 两者似乎都可以工作,但是由于某种原因,我的并行任务比链接它们花费的时间长得多。

I am new to TPL / Parallelism so it may be my code that is incorrect, but wouldn't chaining the requests take longer since each would have to finish before the next started? 我是TPL /并行技术的新手,所以可能是我的代码不正确,但是由于每个请求都必须在下一次启动之前完成,因此链接请求是否需要花费更长的时间? Rather than in Parallel they all go out at once so you only have to wait for the slowest? 而不是并行运行,它们都立即消失了,所以您只需要等待最慢的时间?

Please let me know if you see faults in my logic or code. 如果您看到我的逻辑或代码中的错误,请告诉我。 I am happy with the response time I am getting but I do not understand why. 我对收到的回复时间感到满意,但我不明白为什么。

My "chaining" code: 我的“链接”代码:

        await (Task.Run(() => WindLookup_DoWork()).
            ContinueWith((t) => WindLookup_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => OFAC_DoWork()).ContinueWith((t) => OFAC_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => BCEGS_DoWork()).ContinueWith((t) => BCEGS_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => BOPTerritory_DoWork()).ContinueWith((t) => BOPTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => TerrorismTerritory_DoWork()).ContinueWith((t) => TerrorismTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => ProtectionClass_DoWork()).ContinueWith((t) => ProtectionClass_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => AddressValidation_DoWork()).ContinueWith((t) => AddressValidation_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));

My "Parallel" code: 我的“并行”代码:

        List<Task> taskList = new List<Task>();
        taskList.Add(Task.Run(() => WindLookup_DoWork()).
            ContinueWith((t) => WindLookup_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => BCEGS_DoWork()).
            ContinueWith((t) => BCEGS_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => BOPTerritory_DoWork()).
            ContinueWith((t) => BOPTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => TerrorismTerritory_DoWork()).
            ContinueWith((t) => TerrorismTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => ProtectionClass_DoWork()).
            ContinueWith((t) => ProtectionClass_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => OFAC_DoWork()).
            ContinueWith((t) => OFAC_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => AddressValidation_DoWork()).
            ContinueWith((t) => AddressValidation_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));

        await Task.WhenAll(taskList.ToArray());

I've basically converted my old Background Worker code, which is why there are DoWork methods, and "callback" methods that update the UI. 我基本上已经转换了旧的背景工作程序代码,这就是为什么有DoWork方法和更新UI的“回调”方法的原因。

The DoWork methods call a POST method to an API, and the process results simply populates a text area with the response xml. DoWork方法调用API的POST方法,并且处理结果仅用响应xml填充文本区域。

I have been fiddling with two different ways of doing this, chaining the requests, and shooting all requests off at the same time (in parallel). 我一直在摆弄两种不同的方法,将请求链接起来,并同时(并行)关闭所有请求。

It's important to distinguish between concurrency and parallelism . 区分并发并行很重要。 In your case, you just want to do them all at the same time, which is a form of concurrency . 对于您的情况,您只想同时完成所有操作,这是一种并发形式。 Parallelism is a more specific technique that uses multiple threads to achieve concurrency, which is appropriate for CPU-bound work. 并行是一种更具体的技术,它使用多个线程来实现并发,这适用于CPU密集型工作。 In your case, however, the work is I/O-bound (API requests), and in that situation a more appropriate form of concurrency would be asynchrony. 但是,在您的情况下,工作是受I / O约束的(API请求),在这种情况下,更合适的并发形式将是异步的。 Asynchrony is a form of concurrency without using threads. 异步使用线程的并发形式。

Both seem to work but for some reason my Parallel tasks take significantly longer than when I chain them. 两者似乎都可以工作,但是由于某种原因,我的并行任务比链接它们花费的时间长得多。

I'm not sure why they would be significantly longer, but one common problem is that the number of simultaneous requests are being limited, either on the client side ( ServicePointManager.DefaultConnectionLimit ), or on the server side (see, eg, Concurrent Requests and Session State ). 我不确定为什么它们会显着更长,但是一个常见的问题是,同时请求的数量受到限制,无论是在客户端( ServicePointManager.DefaultConnectionLimit )还是在服务器端(例如,参见并发请求)和会话状态 )。

Please let me know if you see faults in my logic or code. 如果您看到我的逻辑或代码中的错误,请告诉我。

Unfortunately, TPL is rather hard to learn from reference documentation or IntelliSense, because there are so many methods and types that should only be used in very specific situations. 不幸的是,TPL很难从参考文档或IntelliSense中学习,因为有太多的方法和类型只应在非常特殊的情况下使用。 In particular, don't use ContinueWith in your scenario; 特别是, 不要在您的方案中使用ContinueWith it has the same problems StartNew does (both links are to my blog). 它具有StartNew所做相同问题 (两个链接均指向我的博客)。

A better (more reliable and easier to maintain) approach is to introduce some helper methods: 更好(更可靠,更易于维护)的方法是引入一些辅助方法:

async Task WindLookupAsync()
{
  await Task.Run(() => WindLookup_DoWork());
  WindLookup_ProcessResults();
}
// etc. for the others

// Calling code (concurrent):
await Task.WhenAll(
    WindLookupAsync(),
    BCEGSAsync(),
    BOPTerritoryAsync(),
    TerrorismTerritoryAsync(),
    ProtectionClassAsync(),
    OFACAsync(),
    AddressValidationAsync()
);

// Calling code (serial):
await WindLookupAsync();
await BCEGSAsync();
await BOPTerritoryAsync();
await TerrorismTerritoryAsync();
await ProtectionClassAsync();
await OFACAsync();
await AddressValidationAsync();

With the refactored code, there's no need for ContinueWith or explicit TaskScheduler s. 使用重构的代码,就不需要ContinueWith或显式的TaskScheduler

However, each request is still burning a threadpool thread for each request. 但是,每个请求仍在为每个请求刻录线程池线程。 If this is a desktop app, it's not the end of the world, but it's not using the best solution. 如果这是台式机应用程序,那不是世界末日,但它没有使用最好的解决方案。 As I mentioned at the beginning of this answer, a more appropriate fit for this problem would be asynchrony rather than parallelism . 正如我在回答开头提到的那样,更适合解决此问题的方法是异步而不是并行

To make the code asynchronous, you should first start with your POST API call, and change that to use the asynchronous version and call it with await . 要使代码异步,您应该首先从POST API调用开始,然后将其更改为使用异步版本,然后使用await调用。 (Side note: WebClient does have asynchronous methods, but consider changing to HttpClient , which fits async a bit more naturally). (附带说明: WebClient确实具有异步方法,但是请考虑将HttpClient更改为更自然地适合async )。 As soon as you call that POST API with await , this will require your _DoWork methods to become asynchronous (and return Task instead of void ). 一旦使用await调用该POST API,这将要求_DoWork方法变得异步(并返回Task而不是void )。 At that point, you can change the helper methods above to await those methods directly rather than using Task.Run , eg: 在这一点上,您可以更改上面的帮助程序方法以直接await这些方法,而不是使用Task.Run ,例如:

async Task WindLookupAsync()
{
  await WindLookup_DoWork();
  WindLookup_ProcessResults();
}

The calling code (both concurrent and serial versions) remains the same. 调用代码(并发和串行版本)保持不变。

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

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