WaitHandle.WaitAny允许线程有序进入

时间:2017-10-15 19:12:02

标签: c# multithreading autoresetevent

我有一定数量的“浏览器”,每个浏览器都不是线程安全的,因此必须在单个线程上使用。另一方面,我有一长串等待使用这些浏览器的线程。我目前正在做的是有一个AutoResetEvent数组:

public readonly AutoResetEvent[] WaitHandles;

并像这样初始化它们:

WaitHandles = Enumerable.Range(0, Browsers.Count).Select(_ => new AutoResetEvent(true)).ToArray();

所以每个浏览器有一个AutoResetEvent,这允许我为每个线程检索特定的浏览器索引:

public Context WaitForBrowser(int i)
{
    System.Diagnostics.Debug.WriteLine($">>> WILL WAIT: {i}");
    var index = WaitHandle.WaitAny(WaitHandles);
    System.Diagnostics.Debug.WriteLine($">>> ENTERED: {i}");
    return new Context(Browsers[index], WaitHandles[index]);
}

这里的i只是线程等待的索引,因为这些线程在列表中并且具有特定的顺序。我只是为了调试目的而传递它。 Context是一次性的,然后在处理时在等待句柄上调用Set

当我查看我的输出时,我看到我的所有“>>> WILL WAIT:{i}”消息是正确的顺序,因为WaitForBrowser的调用按顺序进行,但我的“ >>> ENTERED:{i}“消息是随机排列的(前几个消息除外),因此他们没有按照他们到达var index = WaitHandle.WaitAny(WaitHandler);行的相同顺序输入。

所以我的问题是,有没有办法修改它,以便线程以相同的顺序输入调用WaitForBrowser方法(这样“>>> ENTERED:{i}”消息是也订购了)?

2 个答案:

答案 0 :(得分:1)

由于似乎不是一个开箱即用的解决方案,我最终使用this solution的修改版本:

public class SemaphoreQueueItem<T> : IDisposable
{
    private bool Disposed;
    private readonly EventWaitHandle WaitHandle;
    public readonly T Resource;

    public SemaphoreQueueItem(EventWaitHandle waitHandle, T resource)
    {
        WaitHandle = waitHandle;
        Resource = resource;
    }

    public void Dispose()
    {
        if (!Disposed)
        {
            Disposed = true;
            WaitHandle.Set();
        }
    }
}

public class SemaphoreQueue<T> : IDisposable
{
    private readonly T[] Resources;
    private readonly AutoResetEvent[] WaitHandles;
    private bool Disposed;
    private ConcurrentQueue<TaskCompletionSource<SemaphoreQueueItem<T>>> Queue = new ConcurrentQueue<TaskCompletionSource<SemaphoreQueueItem<T>>>();

    public SemaphoreQueue(T[] resources)
    {
        Resources = resources;
        WaitHandles = Enumerable.Range(0, resources.Length).Select(_ => new AutoResetEvent(true)).ToArray();
    }

    public SemaphoreQueueItem<T> Wait(CancellationToken cancellationToken)
    {
        return WaitAsync(cancellationToken).Result;
    }

    public Task<SemaphoreQueueItem<T>> WaitAsync(CancellationToken cancellationToken)
    {
        var tcs = new TaskCompletionSource<SemaphoreQueueItem<T>>();
        Queue.Enqueue(tcs);

        Task.Run(() => WaitHandle.WaitAny(WaitHandles.Concat(new[] { cancellationToken.WaitHandle }).ToArray())).ContinueWith(task =>
        {
            if (Queue.TryDequeue(out var popped))
            {
                var index = task.Result;

                if (cancellationToken.IsCancellationRequested)
                    popped.SetResult(null);
                else
                    popped.SetResult(new SemaphoreQueueItem<T>(WaitHandles[index], Resources[index]));
            }
        });

        return tcs.Task;
    }

    public void Dispose()
    {
        if (!Disposed)
        {
            foreach (var handle in WaitHandles)
                handle.Dispose();

            Disposed = true;
        }
    }
}

答案 1 :(得分:0)

Have you considered using Semaphore instead of array of AutoResetEvent ?

Problem with order of waiting threads (for semaphore) was discussed here: Guaranteed semaphore order?的所有href属性值