C# 高效地等待一个或多个资源可用
在我花太长时间重新发明轮子之前,我想检查一下.Net中是否已经有一个类可以满足我的需要 我想要的是有点像信号灯(或者甚至像倒计时事件),但有点不同 我有一个需求,我有不同数量的“资源”可用,我希望线程在没有可用资源时高效地等待。同时,另一个线程可以释放一个资源,这将立即释放另一个等待的线程 这听起来很像一个信号量,但这并不是因为信号量(据我所见)将每个线程视为计数的“资源” 无论如何,这是我想要的第一个简单实现。它还没有处理、代码契约、错误处理、超时支持或取消支持,但它应该展示我想要的:C# 高效地等待一个或多个资源可用,c#,multithreading,task-parallel-library,multitasking,C#,Multithreading,Task Parallel Library,Multitasking,在我花太长时间重新发明轮子之前,我想检查一下.Net中是否已经有一个类可以满足我的需要 我想要的是有点像信号灯(或者甚至像倒计时事件),但有点不同 我有一个需求,我有不同数量的“资源”可用,我希望线程在没有可用资源时高效地等待。同时,另一个线程可以释放一个资源,这将立即释放另一个等待的线程 这听起来很像一个信号量,但这并不是因为信号量(据我所见)将每个线程视为计数的“资源” 无论如何,这是我想要的第一个简单实现。它还没有处理、代码契约、错误处理、超时支持或取消支持,但它应该展示我想要的: pub
public sealed class ResourceCounter
{
/// <summary>Create with the specified number of resources initially available.</summary>
public ResourceCounter(int resourceCount)
{
_resourceCount = resourceCount;
if (_resourceCount > 0)
{
_resourceAvailable.Set();
}
}
/// <summary>Acquires a resource. Waits forever if necessary.</summary>
public void Acquire()
{
while (true)
{
_resourceAvailable.Wait();
lock (_lock)
{
if (_resourceCount > 0)
{
if (--_resourceCount == 0)
{
_resourceAvailable.Reset();
}
return;
}
}
}
}
/// <summary>Releases a resource.</summary>
public void Release()
{
lock (_lock)
{
++_resourceCount;
_resourceAvailable.Set();
}
}
private int _resourceCount;
private readonly object _lock = new object();
private readonly ManualResetEventSlim _resourceAvailable = new ManualResetEventSlim();
}
公共密封类资源计数器
{
///使用指定数量的初始可用资源创建。
公共资源计数器(int resourceCount)
{
_resourceCount=resourceCount;
如果(_resourceCount>0)
{
_resourceAvailable.Set();
}
}
///获取资源。如果需要,将永远等待。
公开无效获取()
{
while(true)
{
_resourceAvailable.Wait();
锁
{
如果(_resourceCount>0)
{
如果(---U resourceCount==0)
{
_Reset();
}
返回;
}
}
}
}
///释放资源。
公开无效释放()
{
锁
{
++_资源计数;
_resourceAvailable.Set();
}
}
私人内部资源计数;
私有只读对象_lock=新对象();
私有只读手册ResetEventSlim\u resourceAvailable=新手册ResetEventSlim();
}
使用模式非常简单:
[编辑]
如下所述,信号量lim做的事情完全正确。我拒绝了它,因为我认为调用Wait()的线程必须是调用Release()的线程,但事实并非如此。这就是我在星期天编码得到的结果…;) 使用队列通信更容易构建多级管道体系结构。生产者线程将项目放入工作队列,一个或多个工作线程将项目出列并处理,然后将它们添加到输出队列。最后一个线程读取输出队列并输出数据 在.NET中,这很容易通过使用来实现 有关两级管道的示例,请参见。添加另一个阶段很简单 为了处理输出线程使事情无序的问题,我使用最小堆将输出队列设置为优先级队列。我的项目由顺序记录编号标识,因此输出线程知道下一个要输出的记录编号。它将在
AutoResetEvent
上等待一个项目放入队列(工作进程将在项目排队时设置事件)。然后,输出线程将查看顶部的项,以查看它是否与预期的项匹配。如果没有,它将再次等待事件
这非常有效,因为它消除了第二个队列。该块位于它所属的输出队列中。表演对我来说非常好。将项目排队是一个O(logn)操作,但实际上n
非常小。即使队列中有100000个项目,与处理记录所需的时间相比,项目排队所需的时间也微不足道
您仍然可以为此使用BlockingCollection
。您只需要让一个二进制堆类实现IProducerConsumerCollection
接口。我是通过向我在中发布的简单二进制堆类添加锁来实现的。然后,您可以向BlockingCollection
构造函数提供其中一个,如下所示:
BlockingCollection<MyRecord> =
new BlockingCollection<ConcurrentBinaryHeap<MyRecord>>(
new ConcurrentBinaryHeap<MyRecord>, MaxQueueSize);
BlockingCollection=
新BlockingCollection(
新的ConcurrentBinaryHeap,MaxQueueSize);
不过,这里有一个潜在的僵局。如果队列已满(即超过初始化BlockingCollection
时设置的最大值),则延迟线程无法将该项排队,所有工作将完全停止。这在实践中从未发生在我身上,因为尽管我的每记录处理时间有所不同,但变化并不大
如果这是一个问题,您可以增加队列大小(只有在您可以肯定地说您永远不会填满队列时才有效),或者