簡體   English   中英

有限並發TaskScheduler,可以交錯要明確排序的任務

[英]Limited concurrency TaskScheduler that can interleave tasks to be explicitly ordered

我正在尋找一個TaskScheduler

  1. 讓我定義一些專用線程(例如8) -一個標准的LimitedConcurrencyLevelTaskScheduler (使用線程池線程)或WorkStealingTaskScheduler做到這一點。
  2. 允許我創建完全排序的子TaskScheduler,但是在父調度程序的專用線程上調度任務。

目前,我們使用TaskScheduler.Default為廣大池(在線程池的增長算法等的支配)和new OrderedTaskScheduler()每當我們要訂購的任務。 我想保持這種行為,但將這兩個要求限制在我自己的專用線程池中。

QueuedTaskScheduler似乎非常接近。 我認為返回子TaskScheduler的QueuedTaskScheduler.ActivateNewQueue()方法將在父工作池上執行任務IN ORDER,但似乎並非如此。 子TaskSchedulers似乎具有與父級相同的並行化級別。

我不一定希望子任務調度程序任務優先於父任務調度程序任務(盡管它將來可能是一個很好的功能)。

我在這里看到了一個相關的問題: 有限的並發級別任務調度程序(具有任務優先級)處理包裝的任務,但我的要求不需要處理異步任務(我的所有排隊任務從頭到尾完全同步,沒有延續)。

我假設“完全有序”你也意味着“一次一個”。

在這種情況下,我相信有一個內置的解決方案應該做得很好: ConcurrentExclusiveSchedulerPair

您的“父”調度程序將是並發調度程序:

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

並且“子”調度程序將是一個獨占調度程序,它使用下面的並發調度程序:

var myScheduler = new ConcurrentExclusiveSchedulerPair(_parent).ExclusiveScheduler;

在仔細考慮了其他答案后,我決定使用它更容易創建自定義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);
}

我希望這對於那些嘗試和我一樣的人來說非常有用,並且可以在單個易於使用的調度程序(可以使用默認的線程池或任何其他調度程序)上將無序任務與有序任務交錯。

===更新===

受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);
}

我理解你的任務有依賴性,這就是你想(部分)訂購它們的原因。 你可以用ContinueWith鏈做到這一點。 您只需要跟蹤任何給定鏈中的最新任務。 當一個新的進入時,您設置該任務的下一個延續並存儲新任務。 你放棄舊的。

替代解決方案:每個鏈都有一個SemaphoreSlim並使用await sem.WaitAsync()來非常靈活地手動控制DOP。 請注意,在一個信號燈不會阻止任何線程異步等待。 它只會占用一點內存。 根本沒有使用OS資源。 您可以使用極多的信號量。

我不認為調度程序是正確的抽象。 調度程序用於基於CPU的工作。 其他協調工具可以與任何Task包括異步IO。 考慮更喜歡普通的任務組合器和協調原語。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM