【问题标题】:Running Tasks in parallel并行运行任务
【发布时间】:2016-02-27 21:29:30
【问题描述】:

我不明白为什么这似乎不能并行运行任务:

var tasks = new Task<MyReturnType>[mbis.Length];

for (int i = 0; i < tasks.Length; i++)
{
     tasks[i] = CAS.Service.GetAllRouterInterfaces(mbis[i], 3);
}

Parallel.ForEach(tasks, task => task.Start());

通过逐步执行,我看到这一行一经评估:

tasks[i] = CAS.Service.GetAllRouterInterfaces(mbis[i], 3);

任务开始。我想将所有新任务添加到列表中,然后并行执行。

【问题讨论】:

  • Don't return "Cold Tasks" fix GetAllRouterInterfaces 并让它返回正在运行的任务(或者它们是否已经在运行,从你的问题很难判断)。
  • 如果GetAllRouterInterfaces 是一个返回Taskasync 方法,它很可能应该被称为GetAllRouterInterfacesAsync

标签: c# task-parallel-library task


【解决方案1】:

如果GetAllRouterInterfacesasync 方法,则生成的Task 将已经启动(请参阅this answer 以获得更多说明)。

这意味着tasks 将包含多个并行运行的任务,而无需随后调用Parallel.ForEach

您可能希望等待tasks 中的所有条目完成,您可以使用await Task.WhenAll(tasks); 完成此操作。

所以你应该得到:

var tasks = new Task<MyReturnType>[mbis.Length];

for (int i = 0; i < tasks.Length; i++)
{
    tasks[i] = CAS.Service.GetAllRouterInterfaces(mbis[i], 3);
}

await Task.WhenAll(tasks);

来自 cmets 的更新

似乎尽管GetAllRouterInterfacesasync 并返回Task,但它仍在发出同步POST 请求(可能在任何其他await 之前)。这可以解释为什么在发出此请求时每次调用 GetAllRouterInterfaces 都会阻塞,因此您获得的并发性最低。理想的解决方案是发出异步 POST 请求,例如:

await webclient.PostAsync(request).ConfigureAwait(false);

这将确保您的for 循环不会被阻塞,并且请求是同时发出的。

对话后进一步更新

您似乎无法使 POST 请求异步,GetAllRouterInterfaces 实际上并没有做任何异步工作,因此我建议如下:

  1. GetAllRouterInterfaces 中删除async 并将返回类型更改为MyReturnType
  2. 像这样并行调用GetAllRouterInterfaces

    var routerInterfaces = mbis.AsParallel()
        .Select(mbi => CAS.Service.GetAllRouterInterfaces(mbi, 3));
    

【讨论】:

  • GetAllRouterInterfaces 返回一个Task,它确实会立即开始做事。出于某种原因,当我并行运行它们而不是逐个运行它们时,我没有看到节省时间的好处。另外,Tasks.WaitAll(tasks.ToArray) 和 await Task.WhenAll(tasks) 有区别吗?
  • @blgrnboy 仅仅因为你并行运行某些东西并不意味着它会更快,特别是如果他们都试图共享一个单一的资源,如磁盘或服务器。
  • @blgrnboy,Task.WaitAll() 正在阻止。它将阻塞线程,直到所有任务完成。
  • 正如@ScottChamberlain 所说,并行运行并不是所有事情都会受益,您的GetAllRouterInterface 可能会遇到阻塞或瓶颈。 await Task.WhenAll(tasks) 应该用在async 方法中,而Task.WaitAll 用于同步方法,会阻塞调用线程。
  • @ScottChamberlain 是的,我理解那部分。他们都在进行 REST 调用,但我认为至少会减少一些时间,因为资源受到其他资源的严重影响并且通常不会减慢速度。
【解决方案2】:

我不知道我是否理解正确。

首先,如果 GetAllRouterInterfaces 返回一个任务,您必须等待结果。

使用 Parallel.ForEach,您不能像现在这样等待任务,但您可以执行类似的操作:

public async Task RunInParallel(IEnumerable<TWhatEver> mbisItems)
{
    //mbisItems == your parameter that you want to pass to GetAllRouterInterfaces

    //degree of cucurrency
    var concurrentTasks = 3;

    //Parallel.Foreach does internally something like this:
    await Task.WhenAll(
        from partition in Partitioner.Create(mbisItems).GetPartitions(concurrentTasks)
        select Task.Run(async delegate
        {
            using (partition)
                while (partition.MoveNext())
                {
                    var currentMbis = partition.Current;
                    var yourResult = await GetAllRouterInterfaces(currentMbis,3);
                }
        }
       ));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-23
    • 2020-06-21
    • 1970-01-01
    • 2020-01-02
    • 2016-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多