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调用
  • 同时在多线程环境中保留不可重入调用处理程序的条件


    我不想更改公共方法签名;例如,应用程序中没有调用方法来发布队列的位置。

    该接口无法完成此操作。您可以异步处理事件订阅以消除死锁,同时仍以串行方式运行它们,但无法保证所描述的顺序。另一个发布调用可能会在事件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条。