我有一定数量的“浏览器”,每个浏览器都不是线程安全的,因此必须在单个线程上使用。另一方面,我有一长串等待使用这些浏览器的线程。我目前正在做的是有一个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}”消息是也订购了)?
答案 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属性值