我想要同时进行7种不同的API调用,并希望在每次调用完成后更新UI。
我一直在摆弄两种不同的方式,将请求链接起来,同时关闭所有请求(并行)。
两者似乎都有效,但出于某种原因,我的并行任务比我链接它们的时间要长得多。
我是TPL / Parallelism的新手,所以它可能是我的代码不正确,但不会链接请求需要更长时间,因为每个都必须在下一次启动之前完成?而不是并行,他们都会立刻出去,所以你只需要等待最慢的?
如果您发现我的逻辑或代码有问题,请告诉我。我对收到的回复时间感到满意,但我不明白为什么。
我的“链接”代码:
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()));
我的“并行”代码:
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());
我基本上已经转换了我的旧后台工作程序代码,这就是为什么有DoWork方法和更新UI的“回调”方法。
DoWork方法将API方法调用到API,并且过程结果只是使用响应xml填充文本区域。
答案 0 :(得分:1)
我一直在摆弄两种不同的方式,将请求链接起来,同时关闭所有请求(并行)。
区分并发和并行非常重要。在您的情况下,您只想同时执行所有操作,这是并发的一种形式。并行性是一种更具体的技术,它使用多个线程来实现并发,这适用于受CPU限制的工作。但是,在您的情况下,工作是I / O绑定(API请求),在这种情况下,更合适的并发形式将是异步的。 Asynchrony 是一种并发的形式,没有使用线程。
两者似乎都有效,但出于某种原因,我的并行任务比我链接它们的时间要长得多。
我不确定为什么他们显着更长,但一个常见问题是同时请求的数量受到限制,无论是在客户端(ServicePointManager.DefaultConnectionLimit
),或在服务器端(参见,例如,Concurrent Requests and Session State)。
如果您发现我的逻辑或代码存在错误,请告诉我。
不幸的是,TPL很难从参考文档或IntelliSense中学习,因为有很多方法和类型只能在非常特定的情况下使用。特别是,您的方案中don't use ContinueWith
;它有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
。
但是,每个请求仍在为每个请求刻录线程池线程。如果这是一个桌面应用程序,它不是世界末日,但它没有使用最佳解决方案。正如我在本回答开头所提到的,更适合这个问题的是异步而不是 parallelism 。
要使代码异步,首先应该从POST API调用开始,然后将其更改为使用异步版本并使用await
调用它。 (旁注:WebClient
确实有异步方法,但请考虑更改为HttpClient
,这更适合async
。只要您使用await
调用POST API,就会要求您的_DoWork
方法变为异步(并返回Task
而不是void
)。此时,您可以直接将上面的帮助方法更改为await
这些方法,而不是使用Task.Run
,例如:
async Task WindLookupAsync()
{
await WindLookup_DoWork();
WindLookup_ProcessResults();
}
调用代码(并发和串行版本)保持不变。