【问题标题】:Executing tasks in parallel并行执行任务
【发布时间】:2015-12-19 21:42:08
【问题描述】:

好的,所以基本上我有一堆任务 (10),我想同时启动它们并等待它们完成。完成后,我想执行其他任务。我阅读了很多关于此的资源,但我无法针对我的特定情况进行正确的处理...

这是我目前拥有的(代码已被简化):

public async Task RunTasks()
{
    var tasks = new List<Task>
    {
        new Task(async () => await DoWork()),
        //and so on with the other 9 similar tasks
    }


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

    Task.WhenAll(tasks).ContinueWith(done =>
    {
        //Run the other tasks
    });
}

//This function perform some I/O operations
public async Task DoWork()
{
    var results = await GetDataFromDatabaseAsync();
    foreach (var result in results)
    {
        await ReadFromNetwork(result.Url);
    }
}

所以我的问题是,当我通过WhenAll 调用等待任务完成时,它告诉我所有任务都已结束,即使它们都没有完成。我尝试在我的foreach 中添加Console.WriteLine,当我进入延续任务时,数据不断从我之前的Tasks 中传入,但尚未真正完成。

我在这里做错了什么?

【问题讨论】:

  • 我怀疑你在某处有一个Task&lt;Task&gt;(直接调用Task构造函数有点可疑),你真的想等待内部任务。您可以使用Task.Run 代替Task 构造函数/Start 吗? Task.Run 将为您解包。
  • 所以你认为我应该这样做: var tasks = new List { Task.Run(()=> DoWork()) }

标签: c# .net asynchronous async-await task-parallel-library


【解决方案1】:

您几乎不应该直接使用Task 构造函数。在您的情况下,该任务只会触发您迫不及待的实际任务。

您可以简单地调用DoWork 并取回一个任务,将其存储在一个列表中并等待所有任务完成。含义:

tasks.Add(DoWork());
// ...
await Task.WhenAll(tasks);

但是,异步方法会同步运行,直到到达未完成任务的第一个 await。如果您担心该部分耗时过长,请使用 Task.Run 将其卸载到另一个 ThreadPool 线程,然后将 任务存储在列表中:

tasks.Add(Task.Run(() => DoWork()));
// ...
await Task.WhenAll(tasks);

【讨论】:

  • 通过同步运行直到第一次等待,你的意思是说它们只有在我们调用Task.WhenAll 时才会真正异步运行?谢谢!
  • @Alisson 我更多地指的是DoWork 的同步部分,而不是调用它的代码。
  • @i3arnon,正如你所说,但是,异步方法会同步运行,直到到达未完成任务的第一个等待。 ---- 所以在这里我可以使用“await task.delay(5).ConfigureAwait(false); 语句作为我的 DoWork() 方法的第一行吗?返回主线程并启动线程池线程以执行 remianing 代码在方法上,?请指教。
  • @SaddamBinSyed 你不需要Task.Delay。你有Task.Yield 正是为了这个目的。但正如我所见,在这种情况下使用Task.Run 会更好,因为它更明确地“启动”新任务。
【解决方案2】:

如果你想使用 TPL 在不同的线程中并行运行这些任务,你可能需要这样的东西:

public async Task RunTasks()
{
    var tasks = new List<Func<Task>>
    {
       DoWork,
       //...
    };

    await Task.WhenAll(tasks.AsParallel().Select(async task => await task()));

    //Run the other tasks
}

这些方法只并行化少量代码:将方法排队到线程池和返回未完成的Task。同样对于如此少量的任务,并行化可能比异步运行花费更多的时间。仅当您的任务在第一次等待之前完成一些较长(同步)的工作时,这才有意义。

在大多数情况下,更好的方法是:

public async Task RunTasks()
{
    await Task.WhenAll(new [] 
    {
        DoWork(),
        //...
    });
    //Run the other tasks
}

我对你的代码的看法:

  1. 在传递给Parallel.ForEach 之前,您不应将代码包装在Task 中。

  2. 您可以只使用await Task.WhenAll 而不是使用ContinueWith

【讨论】:

  • 一个鼓舞人心的代码 sn-p,也许是网络上最好的,它使用 PLINQ 将任务并行与异步结合起来。
  • 作为建议:使用 dotNet 4.7+ 或 Core,您不再需要 new[] {}。相反,您可以只传递多个任务,例如 WhenAll(DoWork(), ....)
【解决方案3】:

本质上,您混合了两种不兼容的异步范式;即Parallel.ForEach()async-await

为了你想要的,做一个或另一个。例如。你可以使用 Parallel.For[Each]() 并完全放弃 async-await。 Parallel.For[Each]() 只会在所有并行任务完成后返回,然后您可以继续执行其他任务。

代码还有一些其他问题:

  • 您将方法标记为异步,但不要在其中等待(您的等待是在委托中,而不是在方法中);

  • 您几乎肯定希望 .ConfigureAwait(false) 在您的等待中,特别是如果您不尝试立即在 UI 线程中使用结果。

【讨论】:

    【解决方案4】:

    DoWork 方法是一种异步 I/O 方法。这意味着您不需要多个线程来执行其中的几个,因为大多数时候该方法将异步等待 I/O 完成。一个线程就足够了。

    public async Task RunTasks()
    {
        var tasks = new List<Task>
        {
            DoWork(),
            //and so on with the other 9 similar tasks
        };
    
        await Task.WhenAll(tasks);
    
        //Run the other tasks            
    }
    

    您几乎应该never use the Task constructor 来创建新任务。要创建异步 I/O 任务,只需调用 async 方法。要创建将在线程池线程上执行的任务,请使用Task.Run。您可以阅读this article 了解Task.Run 和其他创建任务选项的详细说明。

    【讨论】:

      【解决方案5】:

      只需在 Task.WhenAll 周围添加一个 try-catch 块

      注意:抛出一个 System.AggregateException 实例,该实例充当已发生的一个或多个异常的包装器。这对于协调多个任务(如 Task.WaitAll() 和 Task.WaitAny() )的方法很重要,因此 AggregateException 能够将所有异常包装在已发生的正在运行的任务中。

      try
      { 
          Task.WaitAll(tasks.ToArray());  
      }
      catch(AggregateException ex)
      { 
          foreach (Exception inner in ex.InnerExceptions)
          {
          Console.WriteLine(String.Format("Exception type {0} from {1}", inner.GetType(), inner.Source));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-18
        • 2018-03-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多