C# 简单内存消息队列
我们现有的域事件实现限制(通过阻止)一次发布到一个线程,以避免对处理程序的重入调用:C# 简单内存消息队列,c#,queue,domain-driven-design,semaphore,reentrancy,C#,Queue,Domain Driven Design,Semaphore,Reentrancy,我们现有的域事件实现限制(通过阻止)一次发布到一个线程,以避免对处理程序的重入调用: public interface IDomainEvent {} // Marker interface public class Dispatcher : IDisposable { private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); // Subscribe code... public vo
public interface IDomainEvent {} // Marker interface
public class Dispatcher : IDisposable
{
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
// Subscribe code...
public void Publish(IDomainEvent domainEvent)
{
semaphore.Wait();
try
{
// Get event subscriber(s) from concurrent dictionary...
foreach (Action<IDomainEvent> subscriber in eventSubscribers)
{
subscriber(domainEvent);
}
}
finally
{
semaphore.Release();
}
}
// Dispose pattern...
}
public接口IDomainEvent{}//标记接口
公共类调度程序:IDisposable
{
私有只读信号量slim信号量=新信号量slim(1,1);
//订阅代码。。。
公共无效发布(IDomainEvent domainEvent)
{
semaphore.Wait();
尝试
{
//从并发字典获取事件订阅服务器。。。
foreach(eventSubscribers中的操作订阅服务器)
{
订户(域名事件);
}
}
最后
{
semaphore.Release();
}
}
//处理模式。。。
}
如果处理程序发布事件,这将导致死锁
如何重写此命令以序列化对Publish
的调用?换句话说,如果订阅处理程序A发布事件B,我将得到:
我不想更改公共方法签名;例如,应用程序中没有调用方法来发布队列的位置。该接口无法完成此操作。您可以异步处理事件订阅以消除死锁,同时仍以串行方式运行它们,但无法保证所描述的顺序。另一个发布调用可能会在事件A的处理程序运行时但在它发布事件B之前将某个事件(事件C)排入队列。然后事件B在队列中的事件C之后结束 只要处理程序A在获取队列中的项目时与其他客户机处于同等地位,它就必须像其他人一样等待(死锁),或者必须公平地执行(先到先得)。您拥有的界面不允许对这两者进行不同的处理
这并不是说你不能在你的逻辑中想出一些诡计来试图区分它们(例如,基于线程id或其他可识别的东西),但是如果你不同时控制订户代码,沿着这些路线的任何东西都是不可靠的。你必须使发布异步化才能实现这一点。朴素的实现将非常简单:
public class Dispatcher : IDisposable {
private readonly BlockingCollection<IDomainEvent> _queue = new BlockingCollection<IDomainEvent>(new ConcurrentQueue<IDomainEvent>());
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public Dispatcher() {
new Thread(Consume) {
IsBackground = true
}.Start();
}
private List<Action<IDomainEvent>> _subscribers = new List<Action<IDomainEvent>>();
public void AddSubscriber(Action<IDomainEvent> sub) {
_subscribers.Add(sub);
}
private void Consume() {
try {
foreach (var @event in _queue.GetConsumingEnumerable(_cts.Token)) {
try {
foreach (Action<IDomainEvent> subscriber in _subscribers) {
subscriber(@event);
}
}
catch (Exception ex) {
// log, handle
}
}
}
catch (OperationCanceledException) {
// expected
}
}
public void Publish(IDomainEvent domainEvent) {
_queue.Add(domainEvent);
}
public void Dispose() {
_cts.Cancel();
}
}
公共类调度程序:IDisposable{
private readonly BlockingCollection _queue=new BlockingCollection(new ConcurrentQueue());
私有只读CancellationTokenSource _cts=new CancellationTokenSource();
公共调度程序(){
新线程(消耗){
IsBackground=true
}.Start();
}
私有列表_订阅者=新列表();
公共用户(操作子系统){
_添加(sub);
}
私有void(){
试一试{
foreach(var@event在_queue.getconsumineGenumerable(_cts.Token))中){
试一试{
foreach(操作订阅服务器在_订阅服务器中){
订户(@event);
}
}
捕获(例外情况除外){
//日志、句柄
}
}
}
捕获(操作取消异常){
//期望
}
}
公共无效发布(IDomainEvent domainEvent){
_添加(domainEvent);
}
公共空间处置(){
_cts.Cancel();
}
}
我们想出了一种同步执行的方法
public class Dispatcher : IDisposable
{
private readonly ConcurrentQueue<IDomainEvent> queue = new ConcurrentQueue<IDomainEvent>();
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
// Subscribe code...
public void Publish(IDomainEvent domainEvent)
{
queue.Enqueue(domainEvent);
if (IsPublishing)
{
return;
}
PublishQueue();
}
private void PublishQueue()
{
IDomainEvent domainEvent;
while (queue.TryDequeue(out domainEvent))
{
InternalPublish(domainEvent);
}
}
private void InternalPublish(IDomainEvent domainEvent)
{
semaphore.Wait();
try
{
// Get event subscriber(s) from concurrent dictionary...
foreach (Action<IDomainEvent> subscriber in eventSubscribers)
{
subscriber(domainEvent);
}
}
finally
{
semaphore.Release();
}
// Necessary, as calls to Publish during publishing could have queued events and returned.
PublishQueue();
}
private bool IsPublishing
{
get { return semaphore.CurrentCount < 1; }
}
// Dispose pattern for semaphore...
}
公共类调度程序:IDisposable
{
私有只读ConcurrentQueue队列=新ConcurrentQueue();
私有只读信号量slim信号量=新信号量slim(1,1);
//订阅代码。。。
公共无效发布(IDomainEvent domainEvent)
{
排队(domainEvent);
如果(正在发布)
{
返回;
}
PublishQueue();
}
私有void PublishQueue()
{
IDomainEvent域事件;
while(queue.TryDequeue(out-domainEvent))
{
内部发布(domainEvent);
}
}
私有void InternalPublish(IDomainEvent domainEvent)
{
semaphore.Wait();
尝试
{
//从并发字典获取事件订阅服务器。。。
foreach(eventSubscribers中的操作订阅服务器)
{
订户(域名事件);
}
}
最后
{
semaphore.Release();
}
//必要时,因为在发布期间对发布的调用可能已将事件排入队列并返回。
PublishQueue();
}
私人图书出版
{
获取{return semaphore.CurrentCount<1;}
}
//信号量的Dispose模式。。。
}
}ConcurrentQueue@Kevin我熟悉
ConcurrentQueue
,希望它能融入到解决方案中。我被困在我的类上下文中的实现上。你需要同步处理它们吗?也就是说:当发布退出时,所有订阅者都应该已经处理了消息?@Evk我想同步处理它们,但我不确定这是否可行。也许内部发布必须是异步的?为什么称之为“消息队列”,这只是一个事件调度器。它根本没有队列。最重要的线索是第一句话会让这句话成为一个评论,但我没有足够的rep
!这很有帮助。事件的顺序并不重要;我本来希望避免创建线程,但似乎我不能。DavidG我的观点是,我会发表评论,提出一些澄清问题,但由于我不能,我唯一的选择是回答提出的问题,或者继续不参与,没有足够的代表参与。这是第22条军规,因此:/@saucecontrol建议进行一些编辑,这真的不需要很长时间就可以达到50条。