簡單易用的.NET免費開源RabbitMQ操作元件EasyNetQ解析

風靈使發表於2018-11-30

對於目前大多的.NET專案,其實使用的技術棧都是差不多,估計現在很少用控制元件開發專案的了,畢竟一大堆問題。對.NET的專案,目前比較適合的架構ASP.NET MVC,ASP.NET WebAPI,ORM(較多Dapper.NET或者其擴充套件,稍大一些的專案用EF等等),為了提高速度也會採用快取(.NET自帶的Memcache,或者Redis),請求較多的專案,使用Nginx做負載均衡和使用佇列等等。

上面簡單的介紹一下.NET的專案的技術架構,具體的技術根據具體的需求做出選擇。介紹到佇列,很多人都會很熟悉,例如MSMQRabbitMQ等等佇列。既然需要使用佇列,那就要考慮如何使用C#更好的操作佇列。

一.RabbitMQ概述

在現在的專案中,訊息佇列的使用比較的頻繁,訊息佇列的種類也較多,如:ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。訊息佇列中介軟體是分散式系統中重要的元件,主要解決應用耦合,非同步訊息,流量削鋒等問題。實現高效能,高可用,可伸縮和最終一致性架構。是大型分散式系統不可缺少的中介軟體。

在這裡主要介紹RabbitMQ訊息佇列,支援開放的高階訊息佇列協議 (AMQP)。RabbitMQ的特點:強大的應用程式訊息傳遞;使用方便;執行在所有主要作業系統上;支援大量開發人員平臺;開源和商業支援。訊息佇列的模式有兩種模式:P2PPoint to Point),P2P模式包含三個角色:訊息佇列(Queue),傳送者(Sender),接收者(Receiver)。每個訊息都被髮送到一個特定的佇列,接收者從佇列中獲取訊息。佇列保留著訊息,直到他們被消費或超時。Publish/Subscribe(Pub/Sub),包含三個角色主題(Topic),釋出者(Publisher),訂閱者(Subscriber) 。多個釋出者將訊息傳送到Topic,系統將這些訊息傳遞給多個訂閱者。

上面介紹了RabbitMQ的相關特點和模式,更多的知識就不再介紹,需要了解安裝和配置,可以進入官網進行細緻的瞭解。

二.EasyNetQ元件概述

上面介紹了RabbitMQ的應用場景和使用的模式,在.NET的專案開發中,較多的使用MSMQ作為訊息佇列,很多人對於MSMQ的操作比較熟悉,也屬於輕量級的訊息佇列。對於RabbitMQ是較為重量級的訊息佇列,有多個語言的版本,作為.NET開發者對於RabbitMQ的操作可能就比較少。在.NET專案中如何更方便的使用RabbitMQ,在這裡就介紹一個.NET操作RabbitMQ的元件EasyNetQ

EasyNetQ的目標是提供一個使.NET中的RabbitMQ儘可能簡單的庫。在EasyNetQ中訊息應由.NET型別表示,訊息應通過其.NET型別進行路由。EasyNetQ按訊息型別進行路由。釋出訊息時,EasyNetQ會檢查其型別,並根據型別名稱,名稱空間和裝配體給出一個路由金鑰。在消費方面,使用者訂閱型別。訂閱型別後,該型別的訊息將路由到訂戶。預設情況下,EasyNetQ使用Newtonsoft.Json庫將.NET型別序列化為JSON。這具有訊息是人類可讀的優點,因此您可以使用RabbitMQ管理應用程式等工具來除錯訊息問題。

EasyNetQ是在RabbitMQ.Client庫之上提供服務的元件集合。這些操作可以像序列化,錯誤處理,執行緒編組,連線管理等。它們由mini-IoC容器組成。您可以輕鬆地用自己的實現替換任何元件。因此,如果您希望XML序列化而不是內建的JSON,只需編寫一個ISerializer的實現並將其註冊到容器。

以下是官方提供的一個結構圖,這個結構圖可以很好的解析該元件的結構:
在這裡插入圖片描述

三.EasyNetQ元件使用方式

介紹完畢EasyNetQ元件的相關背景,現在就要介紹一下該元件的使用方式。EasyNetQ元件的使用方式比較簡單,跟很多元件都類似,例如:建立連線,進行操作做等等,對於EasyNetQ元件也是如此。

1.建立連線:

var bus = RabbitHutch.CreateBus(“host=myServer;virtualHost=myVirtualHost;username=mike;password=topsecret”);

RabbitMQ伺服器的延遲連線由IBus介面表示,建立連線的方式連線字串由格式為key = value的鍵/值對組成,每一個用分號(;)分隔。host:主機地址;virtualHost:預設是預設的虛擬主機’/’;username:使用者名稱,預設為’guest’;password:密碼,預設是’guest’;

2.關閉連線:

bus.Dispose();

要關閉連線,只需簡單地處理匯流排,這將關閉EasyNetQ使用的連線,渠道,消費者和所有其他資源。

3.釋出訊息:

var message = new MyMessage { Text = "Hello Rabbit" };
bus.Publish(message);

4.訂閱郵件:

bus.Subscribe<MyMessage>("my_subscription_id", msg => Console.WriteLine(msg.Text));

5.遠端過程呼叫:

var request = new TestRequestMessage {Text = "Hello from the client! "};
bus.Request<TestRequestMessage, TestResponseMessage>(request, response => Console.WriteLine("Got response: '{0}'",response.Text));

6.RPC伺服器:

bus.Respond<TestRequestMessage, TestResponseMessage>(request => new TestResponseMessage{ Text = request.Text + " all done!" });

7.記錄器:

var logger = new MyLogger() ;
var bus = RabbitHutch.CreateBus(“my connection string, x => x.Register<IEasyNetQLogger>(_ => logger));

8.路由:

bus.Subscribe("my_id", handler, x => x.WithTopic("X.*"));

RabbitMQ具有非常好的功能,基於主題的路由,允許訂閱者基於多個標準過濾訊息。*(星號)匹配一個字。(雜湊)匹配為零個或多個單詞。

四.EasyNetQ元件核心物件解析

上面簡單的介紹了一下該元件的應用方式,還有比較多的方式沒有做介紹,又需要的可以做深入的瞭解。在這裡介紹一下該元件的一些核心的物件。

1.RabbitHutch.CreateBus()
舊版方法

public static IBus CreateBus(ConnectionConfiguration connectionConfiguration, AdvancedBusEventHandlers advancedBusEventHandlers, 
             Action<IServiceRegister> registerServices)
        {
            Preconditions.CheckNotNull(connectionConfiguration, "connectionConfiguration");
            Preconditions.CheckNotNull(advancedBusEventHandlers, "advancedBusEventHandlers");
            Preconditions.CheckNotNull(registerServices, "registerServices");
            var container = createContainerInternal();
            if (container == null)
            {
                throw new EasyNetQException("Could not create container. " +
                    "Have you called SetContainerFactory(...) with a function that returns null?");
            }
            connectionConfiguration.Validate();
            container.Register(_ => connectionConfiguration);
            container.Register(_ => advancedBusEventHandlers);
            registerServices(container);
            ComponentRegistration.RegisterServices(container);
            return container.Resolve<IBus>();
        }

RabbitHutch類中主要包含的方法是CreateBus()方法,具有12個過載。該方法主要根據使用者的連線配置資訊,連線服務端。該方法接收三個引數,connectionConfiguration表示連線例項,advancedBusEventHandlers用於新增處理程式的AdvancedBusEventHandlers例項到新建立的IBus.Advanced”的事件。registerServices覆蓋預設服務。 ComponentRegistration.RegisterServices(container);在我們內部的超簡單IoC容器中註冊預設的EasyNetQ元件。container.Resolve<IBus>()獲取所請求的服務的例項。 注意所有服務都是單例的,多次通話Resolve將返回相同的例項。


EasyNetQ 3.3.4 新版
RabbitHutch.cs

using System;
using System.Collections.Generic;
#if NETFX
using System.Configuration;
#endif
using EasyNetQ.ConnectionString;
using EasyNetQ.DI;

namespace EasyNetQ
{
    /// <summary>
    /// Static methods to create EasyNetQ core APIs.
    /// </summary>
    public static class RabbitHutch
    {
#if NETFX
        /// <summary>
        /// Creates a new instance of <see cref="RabbitBus"/>.
        /// The RabbitMQ broker is defined in the connection string named 'rabbit'.
        /// </summary>
        /// <param name="registerServices">
        /// Override default services. For example, to override the default <see cref="ISerializer"/>:
        /// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
        /// </param>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static IBus CreateBus(Action<IServiceRegister> registerServices)
        {
            var rabbitConnection = ConfigurationManager.ConnectionStrings["rabbit"];
            if (rabbitConnection == null)
            {
                throw new EasyNetQException(
                    "Could not find a connection string for RabbitMQ. " +
                    "Please add a connection string in the <ConnectionStrings> section" +
                    "of the application's configuration file. For example: " +
                    "<add name=\"rabbit\" connectionString=\"host=localhost\" />");
            }
            var rabbitConnectionString = rabbitConnection.ConnectionString;
            return CreateBus(rabbitConnectionString, registerServices);
        }

        /// <summary>
        /// Creates a new instance of <see cref="RabbitBus"/>.
        /// The RabbitMQ broker is defined in the connection string named 'rabbit'.
        /// </summary>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static IBus CreateBus()
        {
            return CreateBus(c => {});
        }
#endif
        /// <summary>
        /// Creates a new instance of <see cref="RabbitBus"/>.
        /// </summary>
        /// <param name="connectionString">
        /// The EasyNetQ connection string. Example:
        /// host=192.168.1.1;port=5672;virtualHost=MyVirtualHost;username=MyUsername;password=MyPassword;requestedHeartbeat=10
        /// 
        /// The following default values will be used if not specified:
        /// host=localhost;port=5672;virtualHost=/;username=guest;password=guest;requestedHeartbeat=10
        /// </param>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static IBus CreateBus(string connectionString)
        {
            return CreateBus(connectionString, x => { });
        }
        
        /// <summary>
        /// Creates a new instance of <see cref="RabbitBus"/>.
        /// </summary>
        /// <param name="connectionString">
        /// The EasyNetQ connection string. Example:
        /// host=192.168.1.1;port=5672;virtualHost=MyVirtualHost;username=MyUsername;password=MyPassword;requestedHeartbeat=10
        /// 
        /// The following default values will be used if not specified:
        /// host=localhost;port=5672;virtualHost=/;username=guest;password=guest;requestedHeartbeat=10
        /// </param>
        /// <param name="registerServices">
        /// Override default services. For example, to override the default <see cref="ISerializer"/>:
        /// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
        /// </param>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static IBus CreateBus(string connectionString, Action<IServiceRegister> registerServices)
        {
            Preconditions.CheckNotNull(connectionString, "connectionString");
            
            return CreateBus(x => x.Resolve<IConnectionStringParser>().Parse(connectionString), registerServices);
        }
        
        /// <summary>
        /// Creates a new instance of <see cref="RabbitBus"/>.
        /// </summary>
        /// <param name="hostName">
        /// The RabbitMQ broker.
        /// </param>
        /// <param name="hostPort">
        /// The RabbitMQ broker port.
        /// </param>
        /// <param name="virtualHost">
        /// The RabbitMQ virtualHost.
        /// </param>
        /// <param name="username">
        /// The username to use to connect to the RabbitMQ broker.
        /// </param>
        /// <param name="password">
        /// The password to use to connect to the RabbitMQ broker.
        /// </param>
        /// <param name="requestedHeartbeat">
        /// The initially requested heartbeat interval, in seconds; zero for none.
        /// </param>
        /// <param name="registerServices">
        /// Override default services. For example, to override the default <see cref="ISerializer"/>:
        /// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
        /// </param>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static IBus CreateBus(
            string hostName,
            ushort hostPort,
            string virtualHost,
            string username,
            string password,
            ushort requestedHeartbeat,
            Action<IServiceRegister> registerServices)
        {
            Preconditions.CheckNotNull(hostName, "hostName");
            Preconditions.CheckNotNull(virtualHost, "virtualHost");
            Preconditions.CheckNotNull(username, "username");
            Preconditions.CheckNotNull(password, "password");

            var connectionConfiguration = new ConnectionConfiguration
            {
                Hosts = new List<HostConfiguration>
                    {
                        new HostConfiguration { Host = hostName, Port = hostPort }
                    },
                Port = hostPort,
                VirtualHost = virtualHost,
                UserName = username,
                Password = password,
                RequestedHeartbeat = requestedHeartbeat
            };
            return CreateBus(connectionConfiguration, registerServices);
        }

        /// <summary>
        /// Creates a new instance of <see cref="RabbitBus"/>.
        /// </summary>
        /// <param name="connectionConfiguration">
        /// An <see cref="ConnectionConfiguration"/> instance.
        /// </param>
        /// <param name="registerServices">
        /// Override default services. For example, to override the default <see cref="ISerializer"/>:
        /// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
        /// </param>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static IBus CreateBus(ConnectionConfiguration connectionConfiguration, Action<IServiceRegister> registerServices)
        {
            Preconditions.CheckNotNull(connectionConfiguration, "connectionConfiguration");
            
            return CreateBus(_ => connectionConfiguration, registerServices);
        }
        
        /// <summary>
        /// Creates a new instance of <see cref="RabbitBus"/>.
        /// </summary>
        /// <param name="connectionConfigurationFactory">
        /// A factory of <see cref="ConnectionConfiguration"/> instance.
        /// </param>
        /// <param name="registerServices">
        /// Override default services. For example, to override the default <see cref="ISerializer"/>:
        /// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
        /// </param>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static IBus CreateBus(Func<IServiceResolver, ConnectionConfiguration> connectionConfigurationFactory, Action<IServiceRegister> registerServices)
        {
            var container = new DefaultServiceContainer();
            RegisterBus(container, connectionConfigurationFactory, registerServices);
            return container.Resolve<IBus>();
        }
        
        /// <summary>
        /// Registers components of a <see cref="RabbitBus"/>.
        /// </summary>
        /// <param name="serviceRegister"/>
        /// <param name="connectionConfigurationFactory">
        /// A factory of <see cref="ConnectionConfiguration"/> instance.
        /// </param>
        /// <param name="registerServices">
        /// Override default services. For example, to override the default <see cref="ISerializer"/>:
        /// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
        /// </param>
        /// <returns>
        /// A new <see cref="RabbitBus"/> instance.
        /// </returns>
        public static void RegisterBus(IServiceRegister serviceRegister,
                                       Func<IServiceResolver, ConnectionConfiguration> connectionConfigurationFactory,
                                       Action<IServiceRegister> registerServices)
        {
            Preconditions.CheckNotNull(serviceRegister, "serviceRegister");
            Preconditions.CheckNotNull(connectionConfigurationFactory, "connectionConfiguration");
            Preconditions.CheckNotNull(registerServices, "registerServices");
            
            serviceRegister.Register(c =>
            {
                var connectionConfiguration = connectionConfigurationFactory.Invoke(c);
                connectionConfiguration.Validate();
                return connectionConfiguration;
            });
            
            serviceRegister.RegisterDefaultServices();
            registerServices(serviceRegister);
        }
    }
}

IBus介面

using System;
using EasyNetQ.Producer;
using EasyNetQ.Scheduling;

namespace EasyNetQ
{
    /// <summary>
    /// Provides a simple Publish/Subscribe, Request/Response, Send/Receive and Delayed Publish API for a message bus.為訊息匯流排提供簡單的釋出/訂閱,請求/響應,傳送/接收和延遲釋出API。
    /// </summary>
    public interface IBus : IDisposable
    {    
        /// <summary>
        /// Provides a simple Publish/Subscribe API
        ///提供簡單的釋出/訂閱API
        /// </summary>
        IPubSub PubSub { get; }
        
        /// <summary>
        /// Provides a simple Request/Response API
        ///提供簡單的請求/響應API
        /// </summary>
        IRpc Rpc { get; }
        
        /// <summary>
        /// Provides a simple Send/Receive API
        ///提供簡單的傳送/接收API
        /// </summary>
        ISendReceive SendReceive { get; }

        
        /// <summary>
        /// Provides a simple Delayed Publish API
        ///提供簡單的延遲釋出API
        /// </summary>
        IScheduler Scheduler { get; }
        
        /// <summary>
        /// Return the advanced EasyNetQ advanced API.
        ///返回高階EasyNetQ高階API
        /// </summary>
        IAdvancedBus Advanced { get; }
    }
}

2.IBus.Publish()

舊版本的方法

public virtual void Publish<T>(T message, Action<IPublishConfiguration> configure) where T : class
        {
            Preconditions.CheckNotNull(message, "message");
            Preconditions.CheckNotNull(configure, "configure");
            var configuration = new PublishConfiguration(conventions.TopicNamingConvention(typeof(T)));
            configure(configuration);
            var messageType = typeof(T);
            var easyNetQMessage = new Message<T>(message)
            {
                Properties =
                {
                    DeliveryMode = messageDeliveryModeStrategy.GetDeliveryMode(messageType)
                }
            };
            if (configuration.Priority != null)
                easyNetQMessage.Properties.Priority = configuration.Priority.Value;
            if (configuration.Expires != null)
                easyNetQMessage.Properties.Expiration = configuration.Expires.ToString();
            var exchange = publishExchangeDeclareStrategy.DeclareExchange(advancedBus, messageType, ExchangeType.Topic);
            advancedBus.Publish(exchange, configuration.Topic, false, easyNetQMessage);
        }     

該方法用於釋出訊息,該方法是一個虛方法,在子類中可以被重寫。 var configuration = new PublishConfiguration(conventions.TopicNamingConvention(typeof(T)))用於定義釋出資訊的配置,Message定義郵件正文內容。


EasyNetQ 3.3.4 新版
PubSubExtensions擴充套件類

using System;
using System.Threading;
using System.Threading.Tasks;
using EasyNetQ.FluentConfiguration;
using EasyNetQ.Internals;

namespace EasyNetQ.Producer
{
    public static class PubSubExtensions
    {
        /// <summary>
        /// Publishes a message with a topic.
        /// When used with publisher confirms the task completes when the publish is confirmed.
        /// Task will throw an exception if the confirm is NACK'd or times out.
        /// </summary>
        /// <typeparam name="T">The message type</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// /// <param name="message">The message to publish</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns></returns>
        public static Task PublishAsync<T>(this IPubSub pubSub, T message, CancellationToken cancellationToken = default)
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");

            return pubSub.PublishAsync(message, c => {}, cancellationToken);
        }
        
        /// <summary>
        /// Publishes a message with a topic.
        /// When used with publisher confirms the task completes when the publish is confirmed.
        /// Task will throw an exception if the confirm is NACK'd or times out.
        /// </summary>
        /// <typeparam name="T">The message type</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// /// <param name="message">The message to publish</param>
        /// <param name="topic">The topic string</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns></returns>
        public static Task PublishAsync<T>(this IPubSub pubSub, T message, string topic, CancellationToken cancellationToken = default)
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");
            Preconditions.CheckNotNull(topic, "topic");

            return pubSub.PublishAsync(message, c => c.WithTopic(topic), cancellationToken);
        }

        /// <summary>
        /// Publishes a message.
        /// </summary>
        /// <typeparam name="T">The message type</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="message">The message to publish</param>
        /// <param name="cancellationToken">The cancellation token</param>
        public static void Publish<T>(this IPubSub pubSub, T message, CancellationToken cancellationToken = default)
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");

            pubSub.Publish(message, c => { }, cancellationToken);
        }

        /// <summary>
        /// Publishes a message.
        /// </summary>
        /// <typeparam name="T">The message type</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="message">The message to publish</param>
        /// <param name="configure">
        /// Fluent configuration e.g. x => x.WithTopic("*.brighton").WithPriority(2)
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        public static void Publish<T>(this IPubSub pubSub, T message, Action<IPublishConfiguration> configure, CancellationToken cancellationToken = default)
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");

            pubSub.PublishAsync(message, configure, cancellationToken)
                .GetAwaiter()
                .GetResult();
        }

        /// <summary>
        /// Publishes a message with a topic
        /// </summary>
        /// <typeparam name="T">The message type</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="message">The message to publish</param>
        /// <param name="topic">The topic string</param>
        /// <param name="cancellationToken">The cancellation token</param>
        public static void Publish<T>(this IPubSub pubSub, T message, string topic, CancellationToken cancellationToken = default)
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");

            pubSub.Publish(message, c => c.WithTopic(topic), cancellationToken);
        }


        /// <summary>
        /// Subscribes to a stream of messages that match a .NET type.
        /// </summary>
        /// <typeparam name="T">The type to subscribe to</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="subscriptionId">
        /// A unique identifier for the subscription. Two subscriptions with the same subscriptionId
        /// and type will get messages delivered in turn. This is useful if you want multiple subscribers
        /// to load balance a subscription in a round-robin fashion.
        /// </param>
        /// <param name="onMessage">
        /// The action to run when a message arrives. When onMessage completes the message
        /// receipt is Ack'd. All onMessage delegates are processed on a single thread so you should
        /// avoid long running blocking IO operations. Consider using SubscribeAsync
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>
        /// An <see cref="ISubscriptionResult"/>
        /// Call Dispose on it or on its <see cref="ISubscriptionResult.ConsumerCancellation"/> to cancel the subscription.
        /// </returns>
        public static AwaitableDisposable<ISubscriptionResult> SubscribeAsync<T>(
            this IPubSub pubSub,
            string subscriptionId,
            Action<T> onMessage,
            CancellationToken cancellationToken = default
        )
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");

            return pubSub.SubscribeAsync(
                subscriptionId,
                onMessage,
                c => { },
                cancellationToken
            );
        }

        /// <summary>
        /// Subscribes to a stream of messages that match a .NET type.
        /// </summary>
        /// <typeparam name="T">The type to subscribe to</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="subscriptionId">
        /// A unique identifier for the subscription. Two subscriptions with the same subscriptionId
        /// and type will get messages delivered in turn. This is useful if you want multiple subscribers
        /// to load balance a subscription in a round-robin fashion.
        /// </param>
        /// <param name="onMessage">
        /// The action to run when a message arrives. When onMessage completes the message
        /// receipt is Ack'd. All onMessage delegates are processed on a single thread so you should
        /// avoid long running blocking IO operations. Consider using SubscribeAsync
        /// </param>
        /// <param name="configure">
        /// Fluent configuration e.g. x => x.WithTopic("uk.london")
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>
        /// An <see cref="ISubscriptionResult"/>
        /// Call Dispose on it or on its <see cref="ISubscriptionResult.ConsumerCancellation"/> to cancel the subscription.
        /// </returns>
        public static AwaitableDisposable<ISubscriptionResult> SubscribeAsync<T>(
            this IPubSub pubSub,
            string subscriptionId,
            Action<T> onMessage,
            Action<ISubscriptionConfiguration> configure,
            CancellationToken cancellationToken = default
        )
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");
            
            var onMessageAsync = TaskHelpers.FromAction<T>((m, c) => onMessage(m));
            
            return pubSub.SubscribeAsync(
                subscriptionId,
                onMessageAsync,
                configure,
                cancellationToken
            );
        }

        /// <summary>
        /// Subscribes to a stream of messages that match a .NET type.
        /// Allows the subscriber to complete asynchronously.
        /// </summary>
        /// <typeparam name="T">The type to subscribe to</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="subscriptionId">
        /// A unique identifier for the subscription. Two subscriptions with the same subscriptionId
        /// and type will get messages delivered in turn. This is useful if you want multiple subscribers
        /// to load balance a subscription in a round-robin fashion.
        /// </param>
        /// <param name="onMessage">
        /// The action to run when a message arrives. onMessage can immediately return a Task and
        /// then continue processing asynchronously. When the Task completes the message will be
        /// Ack'd.
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>
        /// An <see cref="ISubscriptionResult"/>
        /// Call Dispose on it or on its <see cref="ISubscriptionResult.ConsumerCancellation"/> to cancel the subscription.
        /// </returns>
        public static AwaitableDisposable<ISubscriptionResult> SubscribeAsync<T>(
            IPubSub pubSub,
            string subscriptionId,
            Func<T, Task> onMessage,
            CancellationToken cancellationToken = default
        )
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");
            
            return pubSub.SubscribeAsync<T>(
                subscriptionId,
                (m, c) => onMessage(m),
                c => { },
                cancellationToken
            );
        }

        /// <summary>
        /// Subscribes to a stream of messages that match a .NET type.
        /// </summary>
        /// <typeparam name="T">The type to subscribe to</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="subscriptionId">
        /// A unique identifier for the subscription. Two subscriptions with the same subscriptionId
        /// and type will get messages delivered in turn. This is useful if you want multiple subscribers
        /// to load balance a subscription in a round-robin fashion.
        /// </param>
        /// <param name="onMessage">
        /// The action to run when a message arrives. When onMessage completes the message
        /// receipt is Ack'd. All onMessage delegates are processed on a single thread so you should
        /// avoid long running blocking IO operations. Consider using SubscribeAsync
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>
        /// An <see cref="ISubscriptionResult"/>
        /// Call Dispose on it or on its <see cref="ISubscriptionResult.ConsumerCancellation"/> to cancel the subscription.
        /// </returns>
        public static ISubscriptionResult Subscribe<T>(
            this IPubSub pubSub,
            string subscriptionId,
            Action<T> onMessage,
            CancellationToken cancellationToken = default
        )
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");

            return pubSub.Subscribe(
                subscriptionId,
                onMessage,
                c => { },
                cancellationToken
            );
        }

        /// <summary>
        /// Subscribes to a stream of messages that match a .NET type.
        /// </summary>
        /// <typeparam name="T">The type to subscribe to</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="subscriptionId">
        /// A unique identifier for the subscription. Two subscriptions with the same subscriptionId
        /// and type will get messages delivered in turn. This is useful if you want multiple subscribers
        /// to load balance a subscription in a round-robin fashion.
        /// </param>
        /// <param name="onMessage">
        /// The action to run when a message arrives. When onMessage completes the message
        /// receipt is Ack'd. All onMessage delegates are processed on a single thread so you should
        /// avoid long running blocking IO operations. Consider using SubscribeAsync
        /// </param>
        /// <param name="configure">
        /// Fluent configuration e.g. x => x.WithTopic("uk.london")
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>
        /// An <see cref="ISubscriptionResult"/>
        /// Call Dispose on it or on its <see cref="ISubscriptionResult.ConsumerCancellation"/> to cancel the subscription.
        /// </returns>
        public static ISubscriptionResult Subscribe<T>(
            this IPubSub pubSub,
            string subscriptionId,
            Action<T> onMessage,
            Action<ISubscriptionConfiguration> configure,
            CancellationToken cancellationToken = default
        )
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");

            var onMessageAsync = TaskHelpers.FromAction<T>((m, c) => onMessage(m));
            
            return pubSub.Subscribe(
                subscriptionId,
                onMessageAsync,
                configure,
                cancellationToken
            );
        }

        /// <summary>
        /// Subscribes to a stream of messages that match a .NET type.
        /// Allows the subscriber to complete asynchronously.
        /// </summary>
        /// <typeparam name="T">The type to subscribe to</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="subscriptionId">
        /// A unique identifier for the subscription. Two subscriptions with the same subscriptionId
        /// and type will get messages delivered in turn. This is useful if you want multiple subscribers
        /// to load balance a subscription in a round-robin fashion.
        /// </param>
        /// <param name="onMessage">
        /// The action to run when a message arrives. onMessage can immediately return a Task and
        /// then continue processing asynchronously. When the Task completes the message will be
        /// Ack'd.
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>
        /// An <see cref="ISubscriptionResult"/>
        /// Call Dispose on it or on its <see cref="ISubscriptionResult.ConsumerCancellation"/> to cancel the subscription.
        /// </returns>
        public static ISubscriptionResult Subscribe<T>(
            IPubSub pubSub,
            string subscriptionId,
            Func<T, Task> onMessage,
            CancellationToken cancellationToken = default
        )
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");
            
            return pubSub.Subscribe<T>(
                subscriptionId,
                (m, c) => onMessage(m), 
                c => { },
                cancellationToken
            );
        }
        
        /// <summary>
        /// Subscribes to a stream of messages that match a .NET type.
        /// Allows the subscriber to complete asynchronously.
        /// </summary>
        /// <typeparam name="T">The type to subscribe to</typeparam>
        /// <param name="pubSub">The pubSub instance</param>
        /// <param name="subscriptionId">
        /// A unique identifier for the subscription. Two subscriptions with the same subscriptionId
        /// and type will get messages delivered in turn. This is useful if you want multiple subscribers
        /// to load balance a subscription in a round-robin fashion.
        /// </param>
        /// <param name="onMessage">
        /// The action to run when a message arrives. onMessage can immediately return a Task and
        /// then continue processing asynchronously. When the Task completes the message will be
        /// Ack'd.
        /// </param>
        /// <param name="configure">
        /// Fluent configuration e.g. x => x.WithTopic("uk.london")
        /// </param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>
        /// An <see cref="ISubscriptionResult"/>
        /// Call Dispose on it or on its <see cref="ISubscriptionResult.ConsumerCancellation"/> to cancel the subscription.
        /// </returns>
        public static ISubscriptionResult Subscribe<T>(
            this IPubSub pubSub,
            string subscriptionId,
            Func<T, CancellationToken, Task> onMessage,
            Action<ISubscriptionConfiguration> configure,
            CancellationToken cancellationToken = default
        )
        {
            Preconditions.CheckNotNull(pubSub, "pubSub");
            
            return pubSub.SubscribeAsync(
                subscriptionId,
                onMessage,
                configure,
                cancellationToken
            ).GetAwaiter().GetResult();
        }
    }
}

五.總結

以上是對該元件的簡單的介紹,如果需要了解更多的內容可以自己去深入的學習和研究。知識在於自己的勤奮,他人只是一個簡單的引導。

相關文章