【问题标题】:Queue several tasks with callback使用回调将多个任务排队
【发布时间】:2013-02-17 20:53:07
【问题描述】:

假设我们有一些方法可以做一些大工作。

最初我们不知道有多少个方法(可以是1,也可以是10)。

在代码中是这样的:

public interface IWorker
{
    void DoWork(DataContainer data);
}

还有几个实现这个接口的类。 然后我们有实例列表。

List<IWorker> workers = new List<IWorker>();

我想异步运行这些方法。另外,当所有这些都被执行时,我想要一些回调。

public void Callback()
{
    Console.WriteLine("everything done");
}

有没有办法在不编写自定义包装器的情况下做到这一点?使用 ThreadPool、Tasks、Parallel?

据我所知,并行阻塞线程,直到任务完成,所以这不是一个预先提供的行为。

创建任务时应该有没有参数的方法,正如我所见,这也不好。

在 ThreadPool 中可以使用方法 QueueUserWorkItem,但使用此方法我不会得到一个“完全完成”回调。

当然,我可以制作自己的包装器,使用 ThreadPool 实现所需的功能,但目标是在不编写此类的情况下实现这一点。

有人可以帮忙吗? 谢谢。

【问题讨论】:

    标签: c# multithreading task-parallel-library threadpool


    【解决方案1】:

    您正在寻找Task.WhenAll。创建一堆任务来做你想做的事,然后等待所有任务并继续你的回调。我拆分了 DoWork 方法的异步版本 - 如果您总是要异步调用它,则不一定需要这样做。

    public interface IWorker
    {
        Task DoWorkAsync(string data);
        void DoWork(string data);
    }
    
    public class Worker : IWorker
    {
        public Task DoWorkAsync(string data)
        {
            return Task.Run(() => DoWork(data));
        }
    
        public void DoWork(string data)
        {
            Console.WriteLine(data);
            Thread.Sleep(100);
        }
    }
    
    public class Runner
    {
        public void Callback()
        {
            Console.WriteLine("Everything done");
        }
    
        public void Run()
        {
            var workers = new List<IWorker> {new Worker(), new Worker(), new Worker()};
            var tasks = workers.Select(t => t.DoWorkAsync("some data"));
    
            Task.WhenAll(tasks).ContinueWith(task => Callback());
    
            Console.WriteLine("Waiting");
        }
    }
    

    【讨论】:

      【解决方案2】:

      听起来像是CountdownEvent 类的主要候选人:

      List<IWorker> workers = new List<IWorker>();
      using (CountdownEvent e = new CountdownEvent(workers.Count))
      {
          foreach (IWorker worker in workers)
          {
              // Dynamically increment signal count.
              e.AddCount();
              // run work itself on another thread
              ThreadPool.QueueUserWorkItem(delegate(object state)
              {
                  try
                  {
                      ((IWorker)state[0]).DoWork((DataContainer)state[1]);
                  }
                  finally
                  {
                      e.Signal();
                  }
              },
              // pass required parameters for block of work
              new object[] { worker, dataForWorker });
          }
      
          // wait for all workers to finish
          e.Wait();
          // run callback code
      }
      

      【讨论】:

        【解决方案3】:

        您正在寻找 TPL 和 Task 类。

        为每个操作创建一个Task,然后调用Task.WhenAll获取一个聚合任务

        【讨论】:

        • 是的,这听起来不错,但 Task 在其构造函数中等待一个没有输入参数的方法。而我的方法也有这样的。
        • @steavy 不需要为任务提供输入参数,因为您不需要在创建任务后对其进行调度,在其他工作中,任务调度程序不需要任何东西给代表。相反,只需使用 lambda 关闭您可能需要作为函数参数的任何变量,而不是直接传递值。
        • 谢谢。由于您是第一个建议 Task.WhenAll 的人,因此将其标记为正确答案:)
        猜你喜欢
        • 1970-01-01
        • 2012-07-29
        • 2021-08-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-21
        • 2016-10-19
        • 1970-01-01
        相关资源
        最近更新 更多