我正在寻找TaskScheduler
:
LimitedConcurrencyLevelTaskScheduler
(使用线程池线程)或WorkStealingTaskScheduler
执行此操作。目前,只要我们想要订购任务,我们就会使用TaskScheduler.Default
作为通用池(受线程池增长算法等的支配)和new OrderedTaskScheduler()
。我想保持这种行为,但是将这两个要求限制在我自己的专用线程池中。
QueuedTaskScheduler
似乎非常接近。我认为返回子TaskScheduler的QueuedTaskScheduler.ActivateNewQueue()
方法会在父级的工作池上执行IN ORDER任务,但似乎并非如此。子TaskSchedulers似乎具有与父级相同的并行化级别。
我不一定希望子taskcheduler任务优先于父taskcheduler任务(尽管将来这可能是一个很好的功能)。
我在这里看到了一个相关的问题:Limited concurrency level task scheduler (with task priority) handling wrapped tasks但我的要求不需要处理异步任务(我所有排队的任务从头到尾完全同步,没有延续)。
答案 0 :(得分:7)
我假设完全有序"你的意思是"一次一个"。
在这种情况下,我相信那里有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
一起使用。考虑更喜欢普通的任务组合器和协调原语。