SemaphoreSlim等待优先级

时间:2016-09-13 15:58:24

标签: .net async-await semaphore

我想知道SemaphoreSlim在调用Await时是否有任何优先权。

我一直找不到任何东西,但也许有人之前做过类似的事情。

我的想法是,如果我需要,可以稍后以更高的优先级在信号量上调用await,并且它将允许await首先返回。

2 个答案:

答案 0 :(得分:3)

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

异步锁很少需要优先级。通常,如果你退一步看看更大的图景,这些问题会有更优雅的解决方案。

答案 1 :(得分:1)

这里是PrioritySemaphore<TPriority>类,可以优先获得。在内部,它基于SortedSet集合。

public class PrioritySemaphore<TPriority>
{
    private readonly PriorityQueue _priorityQueue;
    private readonly object _locker = new object();
    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));

        _priorityQueue = new PriorityQueue(comparer);
        _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 { get { lock (_locker) return _currentCount; } }

    public async Task<bool> WaitAsync(TPriority priority, int millisecondsTimeout,
        CancellationToken cancellationToken = default)
    {
        if (millisecondsTimeout < -1)
            throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));

        cancellationToken.ThrowIfCancellationRequested();
        lock (_locker)
        {
            if (_currentCount > 0)
            {
                _currentCount--;
                return true;
            }
        }
        if (millisecondsTimeout == 0) return false;
        var tcs = new TaskCompletionSource<bool>(
            TaskCreationOptions.RunContinuationsAsynchronously);
        long entryIndex = -1;
        bool taskCompleted = false;

        Timer timer = null;
        if (millisecondsTimeout > 0)
        {
            timer = new Timer(_ =>
            {
                bool doComplete;
                lock (_locker)
                {
                    doComplete = entryIndex == -1
                        || _priorityQueue.Remove(priority, entryIndex);
                    if (doComplete) taskCompleted = true;
                }
                if (doComplete) tcs.TrySetResult(false);
            }, null, millisecondsTimeout, Timeout.Infinite);
        }

        CancellationTokenRegistration registration = default;
        if (cancellationToken.CanBeCanceled)
        {
            registration = cancellationToken.Register(() =>
            {
                bool doComplete;
                lock (_locker)
                {
                    doComplete = entryIndex == -1
                        || _priorityQueue.Remove(priority, entryIndex);
                    if (doComplete) taskCompleted = true;
                }
                if (doComplete) tcs.TrySetCanceled(cancellationToken);
            });
        }

        bool disposeSubscriptions = false;
        lock (_locker)
        {
            if (!taskCompleted)
            {
                entryIndex = _indexSeed++;
                _priorityQueue.Enqueue(priority, entryIndex, tcs, timer, registration);
            }
            else
            {
                disposeSubscriptions = true;
            }
        }
        if (disposeSubscriptions)
        {
            timer?.Dispose();
            registration.Dispose();
        }
        return await tcs.Task.ConfigureAwait(false);
    }

    public Task WaitAsync(TPriority priority,
        CancellationToken cancellationToken = default)
    {
        return WaitAsync(priority, Timeout.Infinite, cancellationToken);
    }

    public void Release()
    {
        TaskCompletionSource<bool> tcs;
        Timer timer;
        CancellationTokenRegistration registration;
        lock (_locker)
        {
            if (_priorityQueue.IsEmpty)
            {
                if (_currentCount >= _maxCount) throw new SemaphoreFullException();
                _currentCount++;
                return;
            }
            (tcs, timer, registration) = _priorityQueue.Dequeue();
        }
        tcs.TrySetResult(true);
        timer?.Dispose();
        registration.Dispose();
    }

    private class PriorityQueue : IComparer<(TPriority Priority, long Index,
        TaskCompletionSource<bool>, Timer, CancellationTokenRegistration)>
    {
        private readonly SortedSet<(TPriority Priority, long Index,
            TaskCompletionSource<bool> TCS, Timer Timer,
            CancellationTokenRegistration Registration)> _sortedSet;

        private readonly IComparer<TPriority> _priorityComparer;
        private readonly Comparer<long> _indexComparer = Comparer<long>.Default;

        public PriorityQueue(IComparer<TPriority> comparer)
        {
            _priorityComparer = comparer ?? Comparer<TPriority>.Default;
            _sortedSet = new SortedSet<(TPriority Priority, long Index,
            TaskCompletionSource<bool> TCS, Timer Timer,
            CancellationTokenRegistration Registration)>(this);
        }

        public bool IsEmpty => _sortedSet.Count == 0;

        public void Enqueue(TPriority priority, long index,
            TaskCompletionSource<bool> tcs, Timer timer,
            CancellationTokenRegistration registration)
        {
            _sortedSet.Add((priority, index, tcs, timer, registration));
        }

        public (TaskCompletionSource<bool>, Timer, CancellationTokenRegistration)
            Dequeue()
        {
            Debug.Assert(_sortedSet.Count > 0);
            var entry = _sortedSet.Min;
            _sortedSet.Remove(entry);
            return (entry.TCS, entry.Timer, entry.Registration);
        }

        public bool Remove(TPriority priority, long index)
        {
            return _sortedSet.Remove((priority, index, default, default, default));
        }

        public int Compare((TPriority Priority, long Index,
            TaskCompletionSource<bool>, Timer, CancellationTokenRegistration) x,
            (TPriority Priority, long Index, TaskCompletionSource<bool>, Timer,
            CancellationTokenRegistration) y)
        {
            int result = _priorityComparer.Compare(x.Priority, y.Priority);
            if (result == 0) result = _indexComparer.Compare(x.Index, y.Index);
            return result;
        }
    }
}

用法示例:

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

Release之后,等待者将获得具有最高优先级的信号量。在上面的示例中,它将是优先级为1的等待者。较小的值表示较高的优先级。如果有多个具有相同最高优先级的等待者,则该信号量将由首先请求它的人获取(保持FIFO顺序)。

PrioritySemaphore<TPriority>仅具有异步API。它支持等待超时和CancellationToken,但是这些功能尚未经过广泛测试。