使用FromEventPattern

时间:2018-04-02 22:48:53

标签: c# .net rabbitmq system.reactive

我正在使用Rx框架为消息编写监听器。

我面临的问题是我使用的图书馆使用的是每当邮件到达时发布事件的消费者。

我设法通过Observable.FromEventPattern使用传入的消息,但我对服务器中已有的消息有疑问。

目前我有以下指令链

  1. 创建消费者
  2. 使用FromEventPattern创建一个可观察的序列并应用所需的转换
  3. 告诉消费者开始
  4. 订阅序列
  5. 最简单的解决方案是交换步骤3和4.但由于它们发生在系统的不同组件中,因此我很难这样做。

    理想情况下,我希望在第4步发生时执行第3步(如OnSubscribe方法)。

    感谢您的帮助:)

    PS:要添加更多细节,事件来自RabbitMQ队列,我正在使用RabbitMQ.Client包中的EventingBasicConsumer类。

    Here你可以找到我正在处理的图书馆。具体来说,this是给我带来问题的类/方法。

    修改

    以下是有问题的代码的剥离版本

    void Main()
    {
        var engine = new Engine();
    
        var messages = engine.Start();
    
        messages.Subscribe(m => m.Dump());
    
        Console.ReadLine();
    
        engine.Stop();
    }
    
    public class Engine
    {
        IConnection _connection;
        IModel _channel;
    
        public IObservable<Message> Start()
        {
            var connectionFactory = new ConnectionFactory();
    
            _connection = connectionFactory.CreateConnection();
            _channel = _connection.CreateModel();
    
            EventingBasicConsumer consumer = new EventingBasicConsumer(_channel);
    
            var observable = Observable.FromEventPattern<BasicDeliverEventArgs>(
                                            a => consumer.Received += a, 
                                            a => consumer.Received -= a)
                                        .Select(e => e.EventArgs);
    
            _channel.BasicConsume("a_queue", false, consumer);
    
            return observable.Select(Transform);
        }
    
        private Message Transform(BasicDeliverEventArgs args) => new Message();
    
        public void Stop()
        {
            _channel.Dispose();
            _connection.Dispose();
        }
    }
    
    public class Message { }
    

    我遇到的症状是,由于我在订阅序列之前调用BasicConsume,因此获取RabbitMQ队列中的任何消息,但不会传递给管道。

    因为我没有&#34; autoack&#34; on,一旦程序停止,消息就会返回队列。

2 个答案:

答案 0 :(得分:2)

正如有些人在评论中指出的那样,正如您在问题中所指出的那样,问题是由于您使用RabbitMQ客户端的方式。

为了解决其中一些问题,我实际做的是创建一个ObservableConsumer类。这是目前正在使用的EventingBasicConsumer的替代品。我这样做的一个原因是处理问题中描述的问题,但另一个原因是允许您在单个连接/通道实例之外重用此使用者对象。这样做的好处是,即使存在瞬态连接/通道特性,也可以使下游无功代码保持连接状态。

using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using RabbitMQ.Client;

namespace com.rabbitmq.consumers
{
    public sealed class ObservableConsumer : IBasicConsumer
    {
        private readonly List<string> _consumerTags = new List<string>();
        private readonly object _consumerTagsLock = new object();
        private readonly Subject<Message> _subject = new Subject<Message>();

        public ushort PrefetchCount { get; set; }
        public IEnumerable<string> ConsumerTags { get { return new List<string>(_consumerTags); } }

        /// <summary>
        /// Registers this consumer on the given queue. 
        /// </summary>
        /// <returns>The consumer tag assigned.</returns>
        public string ConsumeFrom(IModel channel, string queueName)
        {
            Model = channel;
            return Model.BasicConsume(queueName, false, this);
        }

        /// <summary>
        /// Contains an observable of the incoming messages where messages are processed on a thread pool thread.
        /// </summary>
        public IObservable<Message> IncomingMessages
        {
            get { return _subject.ObserveOn(Scheduler.ThreadPool); }
        }

        ///<summary>Retrieve the IModel instance this consumer is
        ///registered with.</summary>
        public IModel Model { get; private set; }

        ///<summary>Returns true while the consumer is registered and
        ///expecting deliveries from the broker.</summary>
        public bool IsRunning
        {
            get { return _consumerTags.Count > 0; }
        }

        /// <summary>
        /// Run after a consumer is cancelled.
        /// </summary>
        /// <param name="consumerTag"></param>
        private void OnConsumerCanceled(string consumerTag)
        {

        }

        /// <summary>
        /// Run after a consumer is added.
        /// </summary>
        /// <param name="consumerTag"></param>
        private void OnConsumerAdded(string consumerTag)
        {

        }

        public void HandleBasicConsumeOk(string consumerTag)
        {
            lock (_consumerTagsLock) {
                if (!_consumerTags.Contains(consumerTag))
                    _consumerTags.Add(consumerTag);
            }
        }

        public void HandleBasicCancelOk(string consumerTag)
        {
            lock (_consumerTagsLock) {
                if (_consumerTags.Contains(consumerTag)) {
                    _consumerTags.Remove(consumerTag);
                    OnConsumerCanceled(consumerTag);
                }
            }
        }

        public void HandleBasicCancel(string consumerTag)
        {
            lock (_consumerTagsLock) {
                if (_consumerTags.Contains(consumerTag)) {
                    _consumerTags.Remove(consumerTag);
                    OnConsumerCanceled(consumerTag);
                }
            }
        }

        public void HandleModelShutdown(IModel model, ShutdownEventArgs reason)
        {
            //Don't need to do anything.
        }

        public void HandleBasicDeliver(string consumerTag,
                                       ulong deliveryTag,
                                       bool redelivered,
                                       string exchange,
                                       string routingKey,
                                       IBasicProperties properties,
                                       byte[] body)
        {
            //Hack - prevents the broker from sending too many messages.
            //if (PrefetchCount > 0 && _unackedMessages.Count > PrefetchCount) {
            //    Model.BasicReject(deliveryTag, true);
            //    return;
            //}

            var message = new Message(properties.HeaderFromBasicProperties()) { Content = body };
            var deliveryData = new MessageDeliveryData()
            {
                ConsumerTag = consumerTag,
                DeliveryTag = deliveryTag,
                Redelivered = redelivered,
            };

            message.Tag = deliveryData;

            if (AckMode != AcknowledgeMode.AckWhenReceived) {
                message.Acknowledged += messageAcknowledged;
                message.Failed += messageFailed;
            }

            _subject.OnNext(message);
        }

        void messageFailed(Message message, Exception ex, bool requeue)
        {
            try {
                message.Acknowledged -= messageAcknowledged;
                message.Failed -= messageFailed;

                if (message.Tag is MessageDeliveryData) {
                    Model.BasicNack((message.Tag as MessageDeliveryData).DeliveryTag, false, requeue);
                }
            }
            catch {}
        }

        void messageAcknowledged(Message message)
        {
            try {
                message.Acknowledged -= messageAcknowledged;
                message.Failed -= messageFailed;

                if (message.Tag is MessageDeliveryData) {
                    var ackMultiple = AckMode == AcknowledgeMode.AckAfterAny;
                    Model.BasicAck((message.Tag as MessageDeliveryData).DeliveryTag, ackMultiple);
                }
            }
            catch {}
        }
    }
}

答案 1 :(得分:1)

我认为没有必要实际订阅兔子队列(通过BasicConsume),直到你有可观察的订阅者。现在你正在开始兔子订阅并将项目推送到可观察状态,即使没有人订阅它。

假设我们有这个样本类:

class Events {
    public event Action<string> MessageArrived;

    Timer _timer;
    public void Start()
    {
        Console.WriteLine("Timer starting");
        int i = 0;
        _timer = new Timer(_ => {
            this.MessageArrived?.Invoke(i.ToString());
            i++;
        }, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
    }

    public void Stop() {
        _timer?.Dispose();
        Console.WriteLine("Timer stopped");
    }
}

你现在正在做的事情基本上是:

var ev = new Events();
var ob = Observable.FromEvent<string>(x => ev.MessageArrived += x, x => ev.MessageArrived -= x);               
ev.Start();    
return ob;

你所需要的是可观察的,它确实如此,但只有当有人订阅时才会这样:

return Observable.Create<string>(observer =>
{
    var ev = new Events();
    var ob = Observable.FromEvent<string>(x => ev.MessageArrived += x, x => ev.MessageArrived -= x);
    // first subsribe
    var sub = ob.Subscribe(observer);
    // then start
    ev.Start();
    // when subscription is disposed - unsubscribe from rabbit
    return new CompositeDisposable(sub, Disposable.Create(() => ev.Stop()));
}); 

很好,但是现在每次订阅observable都会导致对兔子队列的单独订阅,这不是我们需要的。我们可以使用Publish().RefCount()解决这个问题:

return Observable.Create<string>(observer => {
    var ev = new Events();
    var ob = Observable.FromEvent<string>(x => ev.MessageArrived += x, x => ev.MessageArrived -= x);
    var sub = ob.Subscribe(observer);                    
    ev.Start();                
    return new CompositeDisposable(sub, Disposable.Create(() => ev.Stop()));
}).Publish().RefCount(); 

现在将发生的事情是当第一个订阅者订阅observable(引用计数从0变为1)时 - 调用来自Observable.Create body的代码并订阅rabbit队列。然后,所有后续订户共享此订阅。当最后取消订阅(引用计数变为零)时 - 处理订阅,调用ev.Stop,并且我们取消订阅兔子队列。

如果发生这种情况,你调用Start()(在你的代码中创建了observable)并且从不订阅它 - 没有任何反应,也没有订阅兔子。