[英]Monitor.Pulse() with a condition
我有一个类,应该是线程安全的。 我最好希望使用单个同步对象来管理线程安全,以避免复杂的思维破坏者,因为所有方法都会改变对象状态变量。 所以我用该对象的lock语句包装方法体。 有一些情况需要释放锁一段时间才能允许另一个线程更新状态。 到目前为止Monitor.Wait()
顺利,只需使用Monitor.Wait()
和Monitor.Pulse()
。 但是,我希望'脉冲'有一个条件。 在下面的代码中,我想仅向在'Send()'方法中等待的线程发送'Pulse'。 同样,只向在'Receive()'方法中等待的线程发送'Pulse'。
总结如下:
CancellationToken
来取消等待。 我尝试了很多东西,包括Monitor,Semaphore和WaitHandle组合,使用WaitHandles的队列和更多创意选项。 此外,我一直在玩多个同步对象。 但在每个场景中,我只能获得部分功能。
下面的代码是我得到的最接近的代码。 TODO评论显示代码有什么问题。
public class Socket
{
public class Item { }
private object sync = new object();
private ManualResetEvent receiveAvailable = new ManualResetEvent(false);
private Queue<Item> receiveQueue = new Queue<Item>();
// used by client, from any thread
public void Send(Item item, CancellationToken token)
{
lock (this.sync)
{
// sends the message somewhere and should await confirmation.
// note that the confirmation order matters.
// TODO: Should only continue on notification from 'NotifySent()', and respect the cancellation token
Monitor.Wait(this.sync);
}
}
// used by client, from any thread
public Item Receive(CancellationToken token)
{
lock (this.sync)
{
if (!this.receiveAvailable.WaitOne(0))
{
// TODO: Should only be notified by 'EnqueueReceived()' method, and respect the cancellation token.
Monitor.Wait(this.sync);
}
var item = this.receiveQueue.Dequeue();
if (this.receiveQueue.Count == 0)
{
this.receiveAvailable.Reset();
}
return item;
}
}
// used by internal worker thread
internal void NotifySent()
{
lock (this.sync)
{
// Should only notify the Send() method.
Monitor.Pulse(this.sync);
}
}
// used by internal worker thread
internal void EnqueueReceived(Item item)
{
lock (this.sync)
{
this.receiveQueue.Enqueue(item);
this.receiveAvailable.Set();
// TODO: Should only notify the 'Receive()' method.
Monitor.Pulse(this.sync);
}
}
}
SIDENOTE:在python中,我可以使用threading.Condition
(忽略CancellationToken
)。 也许C#中有类似的结构?
class Socket(object):
def __init__(self):
self.sync = threading.RLock()
self.receive_queue = collections.deque()
self.send_ready = threading.Condition(self.sync)
self.receive_ready = threading.Condition(self.sync)
def send(self, item):
with self.send_ready:
// send the message
self.send_ready.wait()
def receive(self):
with self.receive_ready:
try:
return self.receive_queue.popleft()
except IndexError:
self.receive_ready.wait()
return self.receive_queue.popleft()
def notify_sent(self):
with self.sync:
self.send_ready.notify()
def enqueue_received(self, item):
with self.sync:
self.receive_queue.append(item)
self.receive_ready.notify()
您正在寻找的是条件变量,它不会直接暴露在任何.NET API中。 该Monitor
是最接近内置型到你要找的是什么,这是一个互斥与单一条件变量相结合。
在.NET中解决此问题的标准方法是在继续之前始终重新检查条件(在等待端)。 这对于处理虚假唤醒也是必要的,这可能发生在所有基于条件变量的解决方案中 。
从而:
// Note: 'while', not 'if'
while (!this.receiveAvailable.WaitOne(0))
{
Monitor.Wait(this.sync);
}
等等。
在.NET中,由于您没有条件变量,因此与指定条件相比,您将获得更多的虚假唤醒,但即使在指定的条件情况下, 也可能发生虚假唤醒。
我相信由于你的意见,我找到了解决问题的方法。 我决定将状态变量分离到外部类,因此锁定套接字并在客户端管理线程安全性变得更容易。 这样我就可以在一个线程中自己管理状态变量(在单独的类中,未在下面的代码中显示)。
这是我提出的综合解决方案:
public class Socket
{
public class Item { }
private class PendingSend
{
public ManualResetEventSlim ManualResetEvent { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
public Exception InnerException { get; set; }
}
private readonly object sendLock = new object();
private readonly object receiveLock = new object();
private readonly ManualResetEventSlim receiveAvailable
= new ManualResetEventSlim(false);
private readonly SemaphoreSlim receiveSemaphore
= new SemaphoreSlim(1, 1);
private readonly ConcurrentQueue<Item> sendQueue
= new ConcurrentQueue<Item>();
private readonly ConcurrentQueue<PendingSend> pendingSendQueue
= new ConcurrentQueue<PendingSend>();
private readonly ConcurrentQueue<Item> receiveQueue
= new ConcurrentQueue<Item>();
// Called from any client thread.
public void Send(Item item, CancellationToken token)
{
// initialize handle to wait for.
using (var handle = new ManualResetEventSlim(false))
{
var pendingSend = new PendingSend
{
ManualResetEvent = handle
};
// Make sure the item and pendingSend are put in the same order.
lock (this.sendLock)
{
this.sendQueue.Enqueue(item);
this.pendingSendQueue.Enqueue(pendingSend);
}
// Wait for the just created send handle to notify.
// May throw operation cancelled, in which case the message is
// still enqueued... Maybe fix that later.
handle.Wait(token);
if (!pendingSend.Success)
{
// Now we actually have information why the send
// failed. Pretty cool.
throw new CommunicationException(
pendingSend.Message,
pendingSend.InnerException);
}
}
}
// Called by internal worker thread.
internal Item DequeueForSend()
{
this.sendQueue.TryDequeue(out Item result);
// May return null, that's fine
return result;
}
// Called by internal worker thread, in the same order items are dequeued.
internal void SendNotification(
bool success,
string message,
Exception inner)
{
if (!this.pendingSendQueue.TryDequeue(out PendingSend result))
{
// TODO: Notify a horrible bug has occurred.
}
result.Success = success;
result.Message = message;
result.InnerException = inner;
// Releases that waithandle in the Send() method.
// The 'PendingSend' instance now contains information about the send.
result.ManualResetEvent.Set();
}
// Called by any client thread.
public Item Receive(CancellationToken token)
{
// This makes sure clients fall through one by one.
this.receiveSemaphore.Wait(token);
try
{
// This makes sure a message is available.
this.receiveAvailable.Wait(token);
if (!this.receiveQueue.TryDequeue(out Item result))
{
// TODO: Log a horrible bug has occurred.
}
// Make sure the count check and the reset happen in a single go.
lock (this.receiveLock)
{
if (this.receiveQueue.Count == 0)
{
this.receiveAvailable.Reset();
}
}
return result;
}
finally
{
// make space for the next receive
this.receiveSemaphore.Release();
}
}
// Called by internal worker thread.
internal void EnqueueReceived(Item item)
{
this.receiveQueue.Enqueue(item);
// Make sure the set and reset don't intertwine
lock (this.receiveLock)
{
this.receiveAvailable.Set();
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.