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

时间:2016-02-16 19:46:42

标签: c# .net multithreading parallel-processing task-parallel-library

我想要同时进行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填充文本区域。

1 个答案:

答案 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();
}

调用代码(并发和串行版本)保持不变。