运行 多个异步任务 - 更快的结果链接与并行?
Running Multiple Async Tasks - Faster results Chaining vs Parallel?
我想同时进行 7 个不同的 API 调用,并希望在每个调用完成后更新 UI。
我一直在摆弄两种不同的方法来做到这一点,链接请求,并同时(并行)关闭所有请求。
两者似乎都有效,但出于某种原因,我的并行任务比链接它们时花费的时间要长得多。
我是 TPL / Parallelism 的新手,所以可能是我的代码不正确,但是链接请求不会花费更长的时间,因为每个请求都必须在下一个开始之前完成吗?而不是并行,它们都同时退出,所以您只需等待最慢的?
如果您发现我的逻辑或代码有错误,请告诉我。我对得到的响应时间很满意,但我不明白为什么。
我的"chaining"代码:
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()));
我的"Parallel"代码:
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());
我基本上已经转换了旧的 Background Worker 代码,这就是为什么有 DoWork 方法和更新 UI 的 "callback" 方法的原因。
DoWork 方法将 POST 方法调用到 API,处理结果只是用响应 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).
区分 并发性 和 并行性 很重要。在您的情况下,您只想同时执行所有操作,这是 并发 的一种形式。并行是一种更具体的技术,它使用多个线程来实现并发,适用于 CPU-bound 工作。但是,在您的情况下,工作是 I/O-bound (API 请求),在这种情况下,更合适的并发形式是异步。 异步 是一种不 使用线程的并发形式。
Both seem to work but for some reason my Parallel tasks take significantly longer than when I chain them.
我不确定为什么它们会 显着 更长,但一个常见的问题是同时请求的数量受到限制,无论是在客户端(ServicePointManager.DefaultConnectionLimit
), or on the server side (see, e.g., Concurrent Requests and Session State).
Please let me know if you see faults in my logic or code.
不幸的是,TPL 很难从参考文档或 IntelliSense 中学习,因为有太多的方法和类型只能在非常特定的情况下使用。特别是,don't use ContinueWith
in your scenario; it has the same problems StartNew
does(两个链接都指向我的博客)。
更好(更可靠和更容易维护)的方法是引入一些辅助方法:
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();
使用重构代码,不需要 ContinueWith
或显式 TaskScheduler
s。
但是,每个请求仍然为每个请求燃烧一个线程池线程。如果这是一个桌面应用程序,它不是世界末日,但它没有使用 最佳 解决方案。正如我在这个答案开头提到的,更适合这个问题的是 asynchrony 而不是 parallelism.
要使代码异步,您应该首先从 POST API 调用开始,然后将其更改为使用异步版本并使用 await
调用它。 (旁注:WebClient
确实有异步方法,但考虑更改为 HttpClient
,这更适合 async
一点)。一旦您使用 await
调用 POST API,这将要求您的 _DoWork
方法变为异步(并且 return Task
而不是void
)。此时,您可以直接将上面的辅助方法更改为 await
那些方法,而不是使用 Task.Run
,例如:
async Task WindLookupAsync()
{
await WindLookup_DoWork();
WindLookup_ProcessResults();
}
调用代码(并发和串行版本)保持不变。
我想同时进行 7 个不同的 API 调用,并希望在每个调用完成后更新 UI。
我一直在摆弄两种不同的方法来做到这一点,链接请求,并同时(并行)关闭所有请求。
两者似乎都有效,但出于某种原因,我的并行任务比链接它们时花费的时间要长得多。
我是 TPL / Parallelism 的新手,所以可能是我的代码不正确,但是链接请求不会花费更长的时间,因为每个请求都必须在下一个开始之前完成吗?而不是并行,它们都同时退出,所以您只需等待最慢的?
如果您发现我的逻辑或代码有错误,请告诉我。我对得到的响应时间很满意,但我不明白为什么。
我的"chaining"代码:
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()));
我的"Parallel"代码:
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());
我基本上已经转换了旧的 Background Worker 代码,这就是为什么有 DoWork 方法和更新 UI 的 "callback" 方法的原因。
DoWork 方法将 POST 方法调用到 API,处理结果只是用响应 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).
区分 并发性 和 并行性 很重要。在您的情况下,您只想同时执行所有操作,这是 并发 的一种形式。并行是一种更具体的技术,它使用多个线程来实现并发,适用于 CPU-bound 工作。但是,在您的情况下,工作是 I/O-bound (API 请求),在这种情况下,更合适的并发形式是异步。 异步 是一种不 使用线程的并发形式。
Both seem to work but for some reason my Parallel tasks take significantly longer than when I chain them.
我不确定为什么它们会 显着 更长,但一个常见的问题是同时请求的数量受到限制,无论是在客户端(ServicePointManager.DefaultConnectionLimit
), or on the server side (see, e.g., Concurrent Requests and Session State).
Please let me know if you see faults in my logic or code.
不幸的是,TPL 很难从参考文档或 IntelliSense 中学习,因为有太多的方法和类型只能在非常特定的情况下使用。特别是,don't use ContinueWith
in your scenario; it has the same problems StartNew
does(两个链接都指向我的博客)。
更好(更可靠和更容易维护)的方法是引入一些辅助方法:
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();
使用重构代码,不需要 ContinueWith
或显式 TaskScheduler
s。
但是,每个请求仍然为每个请求燃烧一个线程池线程。如果这是一个桌面应用程序,它不是世界末日,但它没有使用 最佳 解决方案。正如我在这个答案开头提到的,更适合这个问题的是 asynchrony 而不是 parallelism.
要使代码异步,您应该首先从 POST API 调用开始,然后将其更改为使用异步版本并使用 await
调用它。 (旁注:WebClient
确实有异步方法,但考虑更改为 HttpClient
,这更适合 async
一点)。一旦您使用 await
调用 POST API,这将要求您的 _DoWork
方法变为异步(并且 return Task
而不是void
)。此时,您可以直接将上面的辅助方法更改为 await
那些方法,而不是使用 Task.Run
,例如:
async Task WindLookupAsync()
{
await WindLookup_DoWork();
WindLookup_ProcessResults();
}
调用代码(并发和串行版本)保持不变。