对于目前大多的.NET项目,其实使用的技术栈都是差不多,估计现在很少用控件开发项目的了,毕竟一大堆问题。对.NET的项目,目前比较适合的架构ASP.NET MVC,ASP.NET WebAPI,ORM
(较多Dapper.NET
或者其扩展,稍大一些的项目用EF
等等),为了提高速度也会采用缓存(.NET
自带的Memcache
,或者Redis
),请求较多的项目,使用Nginx
做负载均衡和使用队列等等。
上面简单的介绍一下.NET
的项目的技术架构,具体的技术根据具体的需求做出选择。介绍到队列,很多人都会很熟悉,例如MSMQ
,RabbitMQ
等等队列。既然需要使用队列,那就要考虑如何使用C#
更好的操作队列。
RabbitMQ
概述在现在的项目中,消息队列的使用比较的频繁,消息队列的种类也较多,如:ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ
等。消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。
在这里主要介绍RabbitMQ
消息队列,支持开放的高级消息队列协议 (AMQP
)。RabbitMQ
的特点:强大的应用程序消息传递;使用方便;运行在所有主要操作系统上;支持大量开发人员平台;开源和商业支持。消息队列的模式有两种模式:P2P
(Point 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
组件也是如此。
var bus = RabbitHutch.CreateBus(“host=myServer;virtualHost=myVirtualHost;username=mike;password=topsecret”);
与RabbitMQ
服务器的延迟连接由IBus
接口表示,创建连接的方式连接字符串由格式为key = value
的键/值对组成,每一个用分号(;)分隔。host
:主机地址;virtualHost
:默认是默认的虚拟主机’/’;username
:用户名,默认为’guest’;password
:密码,默认是’guest’;
bus.Dispose();
要关闭连接,只需简单地处理总线,这将关闭EasyNetQ
使用的连接,渠道,消费者和所有其他资源。
var message = new MyMessage { Text = "Hello Rabbit" };
bus.Publish(message);
bus.Subscribe<MyMessage>("my_subscription_id", msg => Console.WriteLine(msg.Text));
var request = new TestRequestMessage {Text = "Hello from the client! "};
bus.Request<TestRequestMessage, TestResponseMessage>(request, response => Console.WriteLine("Got response: '{0}'",response.Text));
bus.Respond<TestRequestMessage, TestResponseMessage>(request => new TestResponseMessage{ Text = request.Text + " all done!" });
var logger = new MyLogger() ;
var bus = RabbitHutch.CreateBus(“my connection string”, x => x.Register<IEasyNetQLogger>(_ => logger));
bus.Subscribe("my_id", handler, x => x.WithTopic("X.*"));
RabbitMQ
具有非常好的功能,基于主题的路由,允许订阅者基于多个标准过滤消息。*
(星号)匹配一个字。#
(哈希)匹配为零个或多个单词。
上面简单的介绍了一下该组件的应用方式,还有比较多的方式没有做介绍,又需要的可以做深入的了解。在这里介绍一下该组件的一些核心的对象。
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
获取所请求的服务的实例。 注意所有服务都是单例的,多次通话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
{
///
/// Static methods to create EasyNetQ core APIs.
///
public static class RabbitHutch
{
#if NETFX
///
/// Creates a new instance of .
/// The RabbitMQ broker is defined in the connection string named 'rabbit'.
///
///
/// Override default services. For example, to override the default :
/// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
///
///
/// A new instance.
///
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 section" +
"of the application's configuration file. For example: " +
" ");
}
var rabbitConnectionString = rabbitConnection.ConnectionString;
return CreateBus(rabbitConnectionString, registerServices);
}
///
/// Creates a new instance of .
/// The RabbitMQ broker is defined in the connection string named 'rabbit'.
///
///
/// A new instance.
///
public static IBus CreateBus()
{
return CreateBus(c => {});
}
#endif
///
/// Creates a new instance of .
///
///
/// 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
///
///
/// A new instance.
///
public static IBus CreateBus(string connectionString)
{
return CreateBus(connectionString, x => { });
}
///
/// Creates a new instance of .
///
///
/// 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
///
///
/// Override default services. For example, to override the default :
/// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
///
///
/// A new instance.
///
public static IBus CreateBus(string connectionString, Action<IServiceRegister> registerServices)
{
Preconditions.CheckNotNull(connectionString, "connectionString");
return CreateBus(x => x.Resolve<IConnectionStringParser>().Parse(connectionString), registerServices);
}
///
/// Creates a new instance of .
///
///
/// The RabbitMQ broker.
///
///
/// The RabbitMQ broker port.
///
///
/// The RabbitMQ virtualHost.
///
///
/// The username to use to connect to the RabbitMQ broker.
///
///
/// The password to use to connect to the RabbitMQ broker.
///
///
/// The initially requested heartbeat interval, in seconds; zero for none.
///
///
/// Override default services. For example, to override the default :
/// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
///
///
/// A new instance.
///
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);
}
///
/// Creates a new instance of .
///
///
/// An instance.
///
///
/// Override default services. For example, to override the default :
/// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
///
///
/// A new instance.
///
public static IBus CreateBus(ConnectionConfiguration connectionConfiguration, Action<IServiceRegister> registerServices)
{
Preconditions.CheckNotNull(connectionConfiguration, "connectionConfiguration");
return CreateBus(_ => connectionConfiguration, registerServices);
}
///
/// Creates a new instance of .
///
///
/// A factory of instance.
///
///
/// Override default services. For example, to override the default :
/// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
///
///
/// A new instance.
///
public static IBus CreateBus(Func<IServiceResolver, ConnectionConfiguration> connectionConfigurationFactory, Action<IServiceRegister> registerServices)
{
var container = new DefaultServiceContainer();
RegisterBus(container, connectionConfigurationFactory, registerServices);
return container.Resolve<IBus>();
}
///
/// Registers components of a .
///
///
///
/// A factory of instance.
///
///
/// Override default services. For example, to override the default :
/// RabbitHutch.CreateBus("host=localhost", x => x.Register{ISerializer}(mySerializer));
///
///
/// A new instance.
///
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
{
///
/// Provides a simple Publish/Subscribe, Request/Response, Send/Receive and Delayed Publish API for a message bus.为消息总线提供简单的发布/订阅,请求/响应,发送/接收和延迟发布API。
///
public interface IBus : IDisposable
{
///
/// Provides a simple Publish/Subscribe API
///提供简单的发布/订阅API
///
IPubSub PubSub { get; }
///
/// Provides a simple Request/Response API
///提供简单的请求/响应API
///
IRpc Rpc { get; }
///
/// Provides a simple Send/Receive API
///提供简单的发送/接收API
///
ISendReceive SendReceive { get; }
///
/// Provides a simple Delayed Publish API
///提供简单的延迟发布API
///
IScheduler Scheduler { get; }
///
/// Return the advanced EasyNetQ advanced API.
///返回高级EasyNetQ高级API
///
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
{
///
/// 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.
///
/// The message type
/// The pubSub instance
/// /// The message to publish
/// The cancellation token
///
public static Task PublishAsync<T>(this IPubSub pubSub, T message, CancellationToken cancellationToken = default)
{
Preconditions.CheckNotNull(pubSub, "pubSub");
return pubSub.PublishAsync(message, c => {}, cancellationToken);
}
///
/// 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.
///
/// The message type
/// The pubSub instance
/// /// The message to publish
/// The topic string
/// The cancellation token
///
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);
}
///
/// Publishes a message.
///
/// The message type
/// The pubSub instance
/// The message to publish
/// The cancellation token
public static void Publish<T>(this IPubSub pubSub, T message, CancellationToken cancellationToken = default)
{
Preconditions.CheckNotNull(pubSub, "pubSub");
pubSub.Publish(message, c => { }, cancellationToken);
}
///
/// Publishes a message.
///
/// The message type
/// The pubSub instance
/// The message to publish
///
/// Fluent configuration e.g. x => x.WithTopic("*.brighton").WithPriority(2)
///
/// The cancellation token
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();
}
///
/// Publishes a message with a topic
///
/// The message type
/// The pubSub instance
/// The message to publish
/// The topic string
/// The cancellation token
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);
}
///
/// Subscribes to a stream of messages that match a .NET type.
///
/// The type to subscribe to
/// The pubSub instance
///
/// 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.
///
///
/// 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
///
/// The cancellation token
///
/// An
/// Call Dispose on it or on its to cancel the subscription.
///
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
);
}
///
/// Subscribes to a stream of messages that match a .NET type.
///
/// The type to subscribe to
/// The pubSub instance
///
/// 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.
///
///
/// 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
///
///
/// Fluent configuration e.g. x => x.WithTopic("uk.london")
///
/// The cancellation token
///
/// An
/// Call Dispose on it or on its to cancel the subscription.
///
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
);
}
///
/// Subscribes to a stream of messages that match a .NET type.
/// Allows the subscriber to complete asynchronously.
///
/// The type to subscribe to
/// The pubSub instance
///
/// 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.
///
///
/// 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.
///
/// The cancellation token
///
/// An
/// Call Dispose on it or on its to cancel the subscription.
///
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
);
}
///
/// Subscribes to a stream of messages that match a .NET type.
///
/// The type to subscribe to
/// The pubSub instance
///
/// 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.
///
///
/// 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
///
/// The cancellation token
///
/// An
/// Call Dispose on it or on its to cancel the subscription.
///
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
);
}
///
/// Subscribes to a stream of messages that match a .NET type.
///
/// The type to subscribe to
/// The pubSub instance
///
/// 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.
///
///
/// 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
///
///
/// Fluent configuration e.g. x => x.WithTopic("uk.london")
///
/// The cancellation token
///
/// An
/// Call Dispose on it or on its to cancel the subscription.
///
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
);
}
///
/// Subscribes to a stream of messages that match a .NET type.
/// Allows the subscriber to complete asynchronously.
///
/// The type to subscribe to
/// The pubSub instance
///
/// 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.
///
///
/// 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.
///
/// The cancellation token
///
/// An
/// Call Dispose on it or on its to cancel the subscription.
///
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
);
}
///
/// Subscribes to a stream of messages that match a .NET type.
/// Allows the subscriber to complete asynchronously.
///
/// The type to subscribe to
/// The pubSub instance
///
/// 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.
///
///
/// 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.
///
///
/// Fluent configuration e.g. x => x.WithTopic("uk.london")
///
/// The cancellation token
///
/// An
/// Call Dispose on it or on its to cancel the subscription.
///
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();
}
}
}
以上是对该组件的简单的介绍,如果需要了解更多的内容可以自己去深入的学习和研究。知识在于自己的勤奋,他人只是一个简单的引导。