SemaphoreSlim 等待 优先级

4

我想知道 SemaphoreSlim 在调用 Await 时是否有类似优先级的功能。

我没有找到任何相关信息,但也许有人之前做过这样的事情。

想法是,如果需要的话,稍后可以使用更高的优先级在 Semaphore 上调用 await,并允许 await 先返回。


1
没有优先级。无论哪匹马先到达终点线,都赢得比赛。在极少数情况下出现平局的情况(这种情况很少发生,因为您不使用WaitAll),操作系统会有意地随机选择获胜者,以对抗锁定车队。http://joeduffyblog.com/2006/12/14/anticonvoy-locks-in-windows-server-2003-sp1-and-windows-vista/ - Hans Passant
谢谢。 我想可能就是这样。 我可以尝试编写一些代码来处理我想要的,但我认为那不是一个很好的主意。 也许有人已经做过类似的事情,但我正在考虑重新编写一些代码。 - JonathanPeel
2个回答

5
这里有一个类 PrioritySemaphore<TPriority>,可以按优先级获取。内部基于 PriorityQueue<TElement, TPriority> 集合实现(.NET 6)。
public class PrioritySemaphore<TPriority>
{
    private readonly PriorityQueue<TaskCompletionSource, (TPriority, long)> _queue;
    private readonly int _maxCount;
    private int _currentCount;
    private long _indexSeed = 0;

    public PrioritySemaphore(int initialCount, int maxCount,
        IComparer<TPriority> comparer = null)
    {
        if (initialCount < 0)
            throw new ArgumentOutOfRangeException(nameof(initialCount));
        if (maxCount <= 0)
            throw new ArgumentOutOfRangeException(nameof(maxCount));

        comparer ??= Comparer<TPriority>.Default;
        _queue = new(Comparer<(TPriority, long)>.Create((x, y) =>
        {
            int result = comparer.Compare(x.Item1, y.Item1);
            if (result == 0) result = x.Item2.CompareTo(y.Item2);
            return result;
        }));
        _currentCount = initialCount;
        _maxCount = maxCount;
    }

    public PrioritySemaphore(int initialCount, IComparer<TPriority> comparer = null)
        : this(initialCount, Int32.MaxValue, comparer) { }

    public PrioritySemaphore(IComparer<TPriority> comparer = null)
        : this(0, Int32.MaxValue, comparer) { }

    public int CurrentCount => Volatile.Read(ref _currentCount);

    public Task WaitAsync(TPriority priority)
    {
        lock (_queue)
        {
            Debug.Assert((_queue.Count == 0) || (_currentCount == 0));
            if (_currentCount > 0)
            {
                _currentCount--;
                return Task.CompletedTask;
            }
            TaskCompletionSource tcs = new(
                TaskCreationOptions.RunContinuationsAsynchronously);
            _queue.Enqueue(tcs, (priority, ++_indexSeed));
            return tcs.Task;
        }
    }

    public void Release()
    {
        TaskCompletionSource tcs;
        lock (_queue)
        {
            Debug.Assert((_queue.Count == 0) || (_currentCount == 0));
            if (_queue.Count == 0)
            {
                if (_currentCount >= _maxCount) throw new SemaphoreFullException();
                _currentCount++;
                return;
            }
            tcs = _queue.Dequeue();
        }
        tcs.TrySetResult();
    }
}

使用示例:

PrioritySemaphore<int> semaphore = new();
//...
await semaphore.WaitAsync(priority: 1);
//...
await semaphore.WaitAsync(priority: 2);
//...
semaphore.Release();

Release 后,信号量将被具有最高优先级的等待者获取。在上面的示例中,它将是具有优先级 1 的等待者。较小的值表示更高的优先级。如果有多个等待者具有相同的最高优先级,则将通过首先请求它的那个等待者来获取信号量。保持 FIFO 顺序是将 TPrioritylong 耦合在上述实现中的原因。

PrioritySemaphore<TPriority> 类仅具有异步 API,并且不支持带有取消或超时的等待。要获取具有更多功能并且还可以编译 .NET 版本早于 6 的版本,请参见此答案的 第五版(基于更灵活但效率较低的 SortedSet)。


4

不,无论您是使用同步还是异步锁定,在SemaphoreSlim中都没有优先级。

使用异步锁很少需要优先级。通常,如果您退后一步并查看更大的图片,这些问题通常有更优雅的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接