有限并发TaskScheduler,可以交错要明确排序的任务

时间:2014-11-04 12:13:51

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

我正在寻找TaskScheduler

  1. 允许我定义一些专用线程(例如8个) - 标准LimitedConcurrencyLevelTaskScheduler(使用线程池线程)或WorkStealingTaskScheduler执行此操作。
  2. 允许我创建完全排序的子TaskScheduler,但在父调度程序的专用线程上安排任务。
  3. 目前,只要我们想要订购任务,我们就会使用TaskScheduler.Default作为通用池(受线程池增长算法等的支配)和new OrderedTaskScheduler()。我想保持这种行为,但是将这两个要求限制在我自己的专用线程池中。

    QueuedTaskScheduler似乎非常接近。我认为返回子TaskScheduler的QueuedTaskScheduler.ActivateNewQueue()方法会在父级的工作池上执行IN ORDER任务,但似乎并非如此。子TaskSchedulers似乎具有与父级相同的并行化级别。

    我不一定希望子taskcheduler任务优先于父taskcheduler任务(尽管将来这可能是一个很好的功能)。

    我在这里看到了一个相关的问题:Limited concurrency level task scheduler (with task priority) handling wrapped tasks但我的要求不需要处理异步任务(我所有排队的任务从头到尾完全同步,没有延续)。

3 个答案:

答案 0 :(得分:7)

我假设完全有序&​​#34;你的意思是"一次一个"。

在这种情况下,我相信那里有built-in solution that should do quite well: ConcurrentExclusiveSchedulerPair

您的父母" scheduler将是一个并发调度程序:

TaskScheduler _parent = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 8)
     .ConcurrentScheduler;

"孩子"调度程序将是一个独占调度程序,它使用下面的并发调度程序:

var myScheduler = new ConcurrentExclusiveSchedulerPair(_parent).ExclusiveScheduler;

答案 1 :(得分:2)

在仔细考虑其他答案后,我决定使用它更容易创建自定义QueuedTaskScheduler,因为我不需要担心异步任务或IO完成(尽管这给了我一些东西)想一下)。

首先,当我们从子工作池中获取工作时,我们在FindNextTask_NeedsLock内添加了一个基于信号量的锁:

var items = queueForTargetTask._workItems;
if (items.Count > 0 
    && queueForTargetTask.TryLock() /* This is added */)
{
    targetTask = items.Dequeue();

对于专用线程版本,在ThreadBasedDispatchLoop

// ... and if we found one, run it
if (targetTask != null)
{
    queueForTargetTask.ExecuteTask(targetTask);
    queueForTargetTask.Release();
}

对于任务计划程序版本,在ProcessPrioritizedAndBatchedTasks

// Now if we finally have a task, run it.  If the task
// was associated with one of the round-robin schedulers, we need to use it
// as a thunk to execute its task.
if (targetTask != null)
{
    if (queueForTargetTask != null)
    {
        queueForTargetTask.ExecuteTask(targetTask);
        queueForTargetTask.Release();
    }
    else
    {
        TryExecuteTask(targetTask);
    }
}

我们在哪里创建新的子队列:

/// <summary>Creates and activates a new scheduling queue for this scheduler.</summary>
/// <returns>The newly created and activated queue at priority 0 and max concurrency of 1.</returns>
public TaskScheduler ActivateNewQueue() { return ActivateNewQueue(0, 1); }

/// <summary>Creates and activates a new scheduling queue for this scheduler.</summary>
/// <param name="priority">The priority level for the new queue.</param>
/// <returns>The newly created and activated queue at the specified priority.</returns>
public TaskScheduler ActivateNewQueue(int priority, int maxConcurrency)
{
    // Create the queue
    var createdQueue = new QueuedTaskSchedulerQueue(priority, maxConcurrency, this);

    ...
}

最后,在嵌套的QueuedTaskSchedulerQueue

// This is added.
private readonly int _maxConcurrency;
private readonly Semaphore _semaphore;

internal bool TryLock()
{
    return _semaphore.WaitOne(0);
}

internal void Release()
{
    _semaphore.Release();
    _pool.NotifyNewWorkItem();
}

/// <summary>Initializes the queue.</summary>
/// <param name="priority">The priority associated with this queue.</param>
/// <param name="maxConcurrency">Max concurrency for this scheduler.</param>
/// <param name="pool">The scheduler with which this queue is associated.</param>
internal QueuedTaskSchedulerQueue(int priority, int maxConcurrency, QueuedTaskScheduler pool)
{
    _priority = priority;
    _pool = pool;
    _workItems = new Queue<Task>();

    // This is added.
    _maxConcurrency = maxConcurrency;
    _semaphore = new Semaphore(_maxConcurrency, _maxConcurrency);
}

我希望这可能对尝试与我做同样事情的人有用,并在单个易于使用的调度程序(可以使用默认的线程池或任何其他调度程序)上将无序任务与有序任务交错。

=== UPDATE ===

受Stephen Cleary的启发,我最终使用了:

private static readonly Lazy<TaskScheduler> Scheduler = new Lazy<TaskScheduler>(
    () => new WorkStealingTaskScheduler(16));

public static TaskScheduler Default
{
    get
    {
        return Scheduler.Value;
    }
}

public static TaskScheduler CreateNewOrderedTaskScheduler()
{
    return new QueuedTaskScheduler(Default, 1);
}

答案 2 :(得分:1)

我理解你的任务有依赖关系,这就是你想(部分)命令它们的原因。你可以用ContinueWith链做到这一点。您只需要跟踪任何给定链中的最新任务。当一个新的进入时,您设置该任务的下一个延续并存储新任务。你放弃旧的。

替代解决方案:每个链一个SemaphoreSlim并使用await sem.WaitAsync()非常灵活地手动控制DOP。注意,异步等待信号量会使阻塞任何线程。它只会占用一点内存。根本没有使用OS资源。你可以使用非常多的信号量。

我认为调度程序不是正确的抽象。调度程序用于基于CPU的工作。其他协调工具可以与包括异步IO在内的任何Task一起使用。考虑更喜欢普通的任务组合器和协调原语。