【问题标题】:Synchronizing heavy and light tasks in C#在 C# 中同步繁重和轻量任务
【发布时间】:2014-01-24 19:09:11
【问题描述】:

我正在尝试实现某种具有不同任务权重的任务队列,允许不同数量的任务根据其权重同时运行。

有两种类型的任务:长任务和短任务。 最多可以同时执行 N 个短任务。

长任务出现时,如果没有其他长任务在运行,则立即启动,否则等待其完成。

如果有长任务运行,并发短任务计数限制应减少到 M。 已经运行的短任务应该继续运行直到完成;但如果当前限制小于或等于当前正在运行的短任务量,则不应启动新的短任务。

看起来,我基本上需要动态改变信号量“容量”的能力。 只需在需要时占用/释放 (N - M) 个“槽”就可以很容易地减少/增加容量,但是如果有 N 个短任务完成,这将导致队列在 (N - M) 个短任务完成之前“挂起”任务已在运行。

我还可以实现某种“调度程序”,每 100 毫秒唤醒一次(例如)并检查队列中是否有任何现在可以开始的任务。这种方法的缺点是在任务入队和启动之间有长达 100 毫秒的延迟。

所以我被这个难题困住了,希望有人对如何实现它有一些新的想法。

更新: 任务不会产生任何显着的 CPU 负载。 它们实际上是 HTTP 请求。长请求是上传文件,短请求是常见的 HTTP 请求。

【问题讨论】:

  • 如果您不想重新发明轮子并使用 .NET 4.5,您可以使用TPL(任务并行库)和Task Schedulers
  • 我不想重新发明轮子。但似乎 TaskScheduler 无法帮助我完成我想要实现的目标。还是我错过了什么?
  • 我已更新问题以添加有关任务性质的更多详细信息。

标签: c# multithreading concurrency task


【解决方案1】:

我回答了一个非常相似的问题a few days ago,您的解决方案几乎完全相同,使用QueuedTaskScheduler 来自“ParallelExtensionsExtras

private static void Main(string[] args)
{
    int highPriorityMaxConcurrancy = 1

    QueuedTaskScheduler qts = new QueuedTaskScheduler();
    var highPriortiyScheduler = qts.ActivateNewQueue(0);
    var lowPriorityScheduler = qts.ActivateNewQueue(1);

    BlockingCollection<HttpRequestWrapper> fileRequest= new BlockingCollection<Foo>();
    BlockingCollection<HttpRequestWrapper> commonRequest= new BlockingCollection<Foo>();

    List<Task> processors = new List<Task>(2);

    processors.Add(Task.Factory.StartNew(() =>
    {
        Parallel.ForEach(fileRequest.GetConsumingPartitioner(),  //.GetConsumingPartitioner() is also from ParallelExtensionExtras, it gives better performance than .GetConsumingEnumerable() with Parallel.ForEeach(
                         new ParallelOptions() { TaskScheduler = highPriortiyScheduler, MaxDegreeOfParallelism = highPriorityMaxConcurrancy }, 
                         ProcessWork);
    }, TaskCreationOptions.LongRunning));

    processors.Add(Task.Factory.StartNew(() =>
    {
        Parallel.ForEach(commonRequest.GetConsumingPartitioner(), 
                         new ParallelOptions() { TaskScheduler = lowPriorityScheduler}, 
                         ProcessWork);
    }, TaskCreationOptions.LongRunning));


    //Add some work to do here to the fileRequest or commonRequest collections


    //Lets the blocking collections know we are no-longer going to be adding new items so it will break out of the `ForEach` once it has finished the pending work.
    fileRequest.CompleteAdding();
    commonRequest.CompleteAdding();

    //Waits for the two collections to compleatly empty before continueing
    Task.WaitAll(processors.ToArray());
}

private static void ProcessWork(HttpRequestWrapper request)
{
    //...
}

【讨论】:

  • 是否也可以为长任务传递 IProgress 并为所有任务传递 CancellationToken?我想我可以将它们作为 HttpRequestWrapper 成员传递并在 ProcessWork 中使用它们?另外,如果没有要运行的任务,Task.WaitAll(processors.ToArray()) 不会自动停止吗?我的目标是让“调度程序”能够等待任务出现,因为一半的时间根本没有任务,它们会不时添加。
  • Task.WaitAll 将在您调用CompleteAdding() 后等待fileRequestcommonRequest 都为空,如果没有待处理的请求,它将立即退出。我只是把它放进来做一个完整的示例控制台程序。我同意我会将任何取消令牌和IProgress 对象放入放入队列中的包装器中,如果您想要单独的作业报告和可取消性,您还需要ProcessWork 中的代码来合作检查是否当前作业已被取消。
  • 说了这么多,您能否在代码中显示您将对这些任务执行什么样的工作?您可能不想使用这种方法,而是使用诸如 IO Completion 端口之类的东西,具体取决于您要执行的操作。
  • 该应用程序是一种GUI工具,供用户将其作品(主要是图像/矢量)上传到某些网站。有许多不同的网络请求以非常随机的方式发出(用户决定何时开始每个网站的流程,有很多网站,每个网站的上传过程都不同 - 有多个步骤 - 授权,上传,填写表格等。 )。所以单个任务将是一些 HTTP 请求(我正在使用带有自定义包装器的 HttpWebRequest)。根据站点的不同,这些请求可能具有非常不同的参数。
  • 大多数情况下,下一次请求相同的图像上传作业是在上一个完成之后开始的(在授权完成之前尝试上传图像是没有意义的,等等)但这并不总是案例(有时我需要从网站下载大约 100 个图像缩略图,我喜欢将它并行到多个线程)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多