{"id":13458053,"url":"https://github.com/Cysharp/MessagePipe","last_synced_at":"2025-03-24T14:33:04.924Z","repository":{"id":37451496,"uuid":"358190188","full_name":"Cysharp/MessagePipe","owner":"Cysharp","description":"High performance in-memory/distributed messaging pipeline for .NET and Unity.","archived":false,"fork":false,"pushed_at":"2025-03-18T08:36:59.000Z","size":2939,"stargazers_count":1515,"open_issues_count":3,"forks_count":110,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-03-18T09:35:52.267Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Cysharp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-15T08:48:15.000Z","updated_at":"2025-03-18T08:37:00.000Z","dependencies_parsed_at":"2024-04-17T07:43:48.253Z","dependency_job_id":"13c95f1f-1989-42ff-b854-feadc3b11132","html_url":"https://github.com/Cysharp/MessagePipe","commit_stats":{"total_commits":253,"total_committers":18,"mean_commits":"14.055555555555555","dds":0.3913043478260869,"last_synced_commit":"87926154f199829065e9c623d01182178f00d3e1"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FMessagePipe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FMessagePipe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FMessagePipe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2FMessagePipe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Cysharp","download_url":"https://codeload.github.com/Cysharp/MessagePipe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245289852,"owners_count":20591145,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T09:00:43.429Z","updated_at":"2025-03-24T14:33:04.304Z","avatar_url":"https://github.com/Cysharp.png","language":"C#","funding_links":[],"categories":["C\\#","C#","C# #"],"sub_categories":[],"readme":"# MessagePipe\n[![GitHub Actions](https://github.com/Cysharp/MessagePipe/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/MessagePipe/actions) [![Releases](https://img.shields.io/github/release/Cysharp/MessagePipe.svg)](https://github.com/Cysharp/MessagePipe/releases)\n\nMessagePipe is a high-performance in-memory/distributed messaging pipeline for .NET and Unity. It supports all cases of Pub/Sub usage, mediator pattern for CQRS, EventAggregator of Prism(V-VM decoupling), IPC(Interprocess Communication)-RPC, etc.\n\n* Dependency-injection first\n* Filter pipeline\n* better event\n* sync/async\n* keyed/keyless\n* buffered/bufferless\n* singleton/scoped\n* broadcast/response(+many)\n* in-memory/interprocess/distributed\n\nMessagePipe is faster than standard C# event and 78 times faster than Prism's EventAggregator.\n\n![](https://user-images.githubusercontent.com/46207/115984507-5d36da80-a5e2-11eb-9942-66602906f499.png)\n\nOf course, memory allocation per publish operation is less(zero).\n\n![](https://user-images.githubusercontent.com/46207/115814615-62542800-a430-11eb-9041-1f31c1ac8464.png)\n\nAlso providing roslyn-analyzer to prevent subscription leak.\n\n![](https://user-images.githubusercontent.com/46207/117535259-da753d00-b02f-11eb-9818-0ab5ef3049b1.png)\n\nGetting Started\n---\nFor .NET, use NuGet. For Unity, please read [Unity](#unity) section.\n\n\u003e PM\u003e Install-Package [MessagePipe](https://www.nuget.org/packages/MessagePipe)\n\nMessagePipe is built on top of a `Microsoft.Extensions.DependencyInjection`(for Unity, `VContainer` or `Zenject` or `Builtin Tiny DI`) so set up via `ConfigureServices` in [.NET Generic Host](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host). Generic Host is widely used in .NET such as ASP.NET Core, [MagicOnion](https://github.com/Cysharp/MagicOnion/), [ConsoleAppFramework](https://github.com/Cysharp/ConsoleAppFramework/), MAUI, WPF(with external support), etc so easy to setup.\n\n```csharp\nusing MessagePipe;\nusing Microsoft.Extensions.DependencyInjection;\n\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        services.AddMessagePipe(); // AddMessagePipe(options =\u003e { }) for configure options\n    })\n```\n\nGet the `IPublisher\u003cT\u003e` for publisher, Get the `ISubscribe\u003cT\u003e` for subscriber, like a `Logger\u003cT\u003e`. `T` can be any type, primitive(int, string, etc...), struct, class, enum, etc.\n\n```csharp\nusing MessagePipe;\n\npublic struct MyEvent { }\n\npublic class SceneA\n{\n    readonly IPublisher\u003cMyEvent\u003e publisher;\n    \n    public SceneA(IPublisher\u003cMyEvent\u003e publisher)\n    {\n        this.publisher = publisher;\n    }\n\n    void Send()\n    {\n        this.publisher.Publish(new MyEvent());\n    }\n}\n\npublic class SceneB\n{\n    readonly ISubscriber\u003cMyEvent\u003e subscriber;\n    readonly IDisposable disposable;\n\n    public SceneB(ISubscriber\u003cMyEvent\u003e subscriber)\n    {\n        var bag = DisposableBag.CreateBuilder(); // composite disposable for manage subscription\n        \n        subscriber.Subscribe(x =\u003e Console.WriteLine(\"here\")).AddTo(bag);\n\n        disposable = bag.Build();\n    }\n\n    void Close()\n    {\n        disposable.Dispose(); // unsubscribe event, all subscription **must** Dispose when completed\n    }\n}\n```\n\nIt is similar to event, but decoupled by type as key. The return value of Subscribe is `IDisposable`, which makes it easier to unsubscribe than event. You can release many subscriptions at once by `DisposableBag`(`CompositeDisposable`). See the [Managing Subscription and Diagnostics](#managing-subscription-and-diagnostics) section for more details.\n\nThe publisher/subscriber(internally we called MessageBroker) is managed by DI, it is possible to have different broker for each scope. Also, all subscriptions are unsubscribed when the scope is disposed, which prevents subscription leaks.\n\n\u003e Default is singleton, you can configure `MessagePipeOptions.InstanceLifetime` to `Singleton` or `Scoped`.\n\n`IPublisher\u003cT\u003e/ISubscriber\u003cT\u003e` is keyless(type only) however MessagePipe has similar interface `IPublisher\u003cTKey, TMessage\u003e/ISubscriber\u003cTKey, TMessage\u003e` that is keyed(topic) interface.\n\nFor example, our real usecase, There is an application that connects Unity and [MagicOnion](https://github.com/Cysharp/MagicOnion/) (a real-time communication framework like SignalR) and delivers it via a browser by Blazor. At that time, we needed something to connect Blazor's page (Browser lifecycle) and MagicOnion's Hub (Connection lifecycle) to transmit data. We also need to distribute the connections by their IDs.\n\n`Browser \u003c-\u003e Blazor \u003c- [MessagePipe] -\u003e MagicOnion \u003c-\u003e Unity`\n\nWe solved this with the following code.\n\n```csharp\n// MagicOnion(similar as SignalR, realtime event framework for .NET and Unity)\npublic class UnityConnectionHub : StreamingHubBase\u003cIUnityConnectionHub, IUnityConnectionHubReceiver\u003e, IUnityConnectionHub\n{\n    readonly IPublisher\u003cGuid, UnitEventData\u003e eventPublisher;\n    readonly IPublisher\u003cGuid, ConnectionClose\u003e closePublisher;\n    Guid id;\n\n    public UnityConnectionHub(IPublisher\u003cGuid, UnitEventData\u003e eventPublisher, IPublisher\u003cGuid, ConnectionClose\u003e closePublisher)\n    {\n        this.eventPublisher = eventPublisher;\n        this.closePublisher = closePublisher;\n    }\n\n    override async ValueTask OnConnected()\n    {\n        this.id = Guid.Parse(Context.Headers[\"id\"]);\n    }\n\n    override async ValueTask OnDisconnected()\n    {\n        this.closePublisher.Publish(id, new ConnectionClose()); // publish to browser(Blazor)\n    }\n\n    // called from Client(Unity)\n    public Task\u003cUnityEventData\u003e SendEventAsync(UnityEventData data)\n    {\n        this.eventPublisher.Publish(id, data); // publish to browser(Blazor)\n    }\n}\n\n// Blazor\npublic partial class BlazorPage : ComponentBase, IDisposable\n{\n    [Parameter]\n    public Guid ID { get; set; }\n\n    [Inject]\n    ISubscriber\u003cGuid, UnitEventData\u003e UnityEventSubscriber { get; set; }\n\n    [Inject]\n    ISubscriber\u003cGuid, ConnectionClose\u003e ConnectionCloseSubscriber { get; set; }\n\n    IDisposable subscription;\n\n    protected override void OnInitialized()\n    {\n        // receive event from MagicOnion(that is from Unity)\n        var d1 = UnityEventSubscriber.Subscribe(ID, x =\u003e\n        {\n            // do anything...\n        });\n\n        var d2 = ConnectionCloseSubscriber.Subscribe(ID, _ =\u003e\n        {\n            // show disconnected thing to view...\n            subscription?.Dispose(); // and unsubscribe events.\n        });\n\n        subscription = DisposableBag.Create(d1, d2); // combine disposable.\n    }\n    \n    public void Dispose()\n    {\n        // unsubscribe event when browser is closed.\n        subscription?.Dispose();\n    }\n}\n```\n\n\u003e The main difference of Reactive Extensions' Subject is has no `OnCompleted`. OnCompleted may or may not be used, making it very difficult to determine the intent to the observer(subscriber). Also, we usually subscribe to multiple events from the same (different event type)publisher, and it is difficult to handle duplicate OnCompleted in that case. For this reason, MessagePipe only provides a simple Publish(OnNext). If you want to convey completion, please receive a separate event and perform dedicated processing there.\n\n\u003e In other words, this is the equivalent of [Relay in RxSwift](https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Subjects.md).\n\nIn addition to standard Pub/Sub, MessagePipe supports async handlers, mediator patterns with handlers that accept return values, and filters for pre-and-post execution customization.\n\nThis image is a visualization of the connection between all those interfaces.\n\n![image](https://user-images.githubusercontent.com/46207/122254092-bf87c980-cf07-11eb-8bdd-039c87309db6.png)\n\nYou may be confused by the number of interfaces, but many functions can be written with a similar, unified API.\n\nPublish/Subscribe\n---\nPublish/Subscribe interface has keyed(topic) and keyless, sync and async interface.\n\n```csharp\n// keyless-sync\npublic interface IPublisher\u003cTMessage\u003e\n{\n    void Publish(TMessage message);\n}\n\npublic interface ISubscriber\u003cTMessage\u003e\n{\n    IDisposable Subscribe(IMessageHandler\u003cTMessage\u003e handler, params MessageHandlerFilter\u003cTMessage\u003e[] filters);\n}\n\n// keyless-async\npublic interface IAsyncPublisher\u003cTMessage\u003e\n{\n    // async interface's publish is fire-and-forget\n    void Publish(TMessage message, CancellationToken cancellationToken = default(CancellationToken));\n    ValueTask PublishAsync(TMessage message, CancellationToken cancellationToken = default(CancellationToken));\n    ValueTask PublishAsync(TMessage message, AsyncPublishStrategy publishStrategy, CancellationToken cancellationToken = default(CancellationToken));\n}\n\npublic interface IAsyncSubscriber\u003cTMessage\u003e\n{\n    IDisposable Subscribe(IAsyncMessageHandler\u003cTMessage\u003e asyncHandler, params AsyncMessageHandlerFilter\u003cTMessage\u003e[] filters);\n}\n\n// keyed-sync\npublic interface IPublisher\u003cTKey, TMessage\u003e\n    where TKey : notnull\n{\n    void Publish(TKey key, TMessage message);\n}\n\npublic interface ISubscriber\u003cTKey, TMessage\u003e\n    where TKey : notnull\n{\n    IDisposable Subscribe(TKey key, IMessageHandler\u003cTMessage\u003e handler, params MessageHandlerFilter\u003cTMessage\u003e[] filters);\n}\n\n// keyed-async\npublic interface IAsyncPublisher\u003cTKey, TMessage\u003e\n    where TKey : notnull\n{\n    void Publish(TKey key, TMessage message, CancellationToken cancellationToken = default(CancellationToken));\n    ValueTask PublishAsync(TKey key, TMessage message, CancellationToken cancellationToken = default(CancellationToken));\n    ValueTask PublishAsync(TKey key, TMessage message, AsyncPublishStrategy publishStrategy, CancellationToken cancellationToken = default(CancellationToken));\n}\n\npublic interface IAsyncSubscriber\u003cTKey, TMessage\u003e\n    where TKey : notnull\n{\n    IDisposable Subscribe(TKey key, IAsyncMessageHandler\u003cTMessage\u003e asyncHandler, params AsyncMessageHandlerFilter\u003cTMessage\u003e[] filters);\n}\n```\n\nAll are available in the form of `IPublisher/Subscribe\u003cT\u003e` in the DI. async handler can await all subscribers completed by `await PublishAsync`. Asynchronous methods can work sequentially or in parallel, depending on `AsyncPublishStrategy` (defaults is `Parallel`, can be changed by `MessagePipeOptions` or by specifying at publish time). If you don't need to wait, you can call `void Publish` to act as fire-and-forget.\n\nThe before and after of execution can be changed by passing a custom filter. See the [Filter](#filter) section for details.\n\nIf an error occurs, it will be propagated to the caller and subsequent subscribers will be stopped. This behavior can be changed by writing a filter to ignore errors.\n\nISingleton***, IScoped***\n---\nI(Async)Publisher(Subscriber)'s lifetime belongs to `MessagePipeOptions.InstanceLifetime`. However if declare with `ISingletonPublisher\u003cTMessage\u003e`/`ISingletonSubscriber\u003cTKey, TMessage\u003e`, `ISingletonAsyncPublisher\u003cTMessage\u003e`/`ISingletonAsyncSubscriber\u003cTKey, TMessage\u003e` then used singleton lifetime. Also `IScopedPublisher\u003cTMessage\u003e`/`IScopedSubscriber\u003cTKey, TMessage\u003e`, `IScopedAsyncPublisher\u003cTMessage\u003e`/`IScopedAsyncSubscriber\u003cTKey, TMessage\u003e` uses scoped lifetime.\n\nBuffered\n---\n`IBufferedPublisher\u003cTMessage\u003e/IBufferedSubscriber\u003cTMessage\u003e` pair is similar as `BehaviorSubject` or Reactive Extensions(More equal is RxSwift's `BehaviorRelay`). It returns latest value on `Subscribe`.\n\n```csharp\nvar p = provider.GetRequiredService\u003cIBufferedPublisher\u003cint\u003e\u003e();\nvar s = provider.GetRequiredService\u003cIBufferedSubscriber\u003cint\u003e\u003e();\n\np.Publish(999);\n\nvar d1 = s.Subscribe(x =\u003e Console.WriteLine(x)); // 999\np.Publish(1000); // 1000\n\nvar d2 = s.Subscribe(x =\u003e Console.WriteLine(x)); // 1000\np.Publish(9999); // 9999, 9999\n\nDisposableBag.Create(d1, d2).Dispose();\n```\n\n\u003e If `TMessage` is class and does not have latest value(null), does not send value on Subscribe.\n\n\u003e Keyed buffered publisher/subscriber does not exist because difficult to avoid memory leak of (unused)key and keep latest value.\n\nEventFactory\n---\nUsing `EventFactory`, you can create generic `IPublisher/ISubscriber`, `IAsyncPublisher/IAsyncSubscriber`, `IBufferedPublisher/IBufferedSubscriber`, `IBufferedAsyncPublisher/IBufferedAsyncSubscriber` like C# events, with a Subscriber tied to each instance, not grouped by type.\n\nMessagePipe has better properties than a normal C# event\n\n* Using Subscribe/Dispose instead of `+=`, `-=` , easy to management subscription\n* Both sync and async support\n* Both bufferless and buffered support\n* Enable unsubscribe all subscription from publisher.dispose\n* Attaches invocation pipeline behaviour by Filter\n* To monitor subscription leak by `MessagePipeDiagnosticsInfo`\n* TO prevent subscription leak by `MessagePipe.Analyzer`\n\n```csharp\npublic class BetterEvent : IDisposable\n{\n    // using MessagePipe instead of C# event/Rx.Subject\n    // store Publisher to private field(declare IDisposablePublisher/IDisposableAsyncPublisher)\n    IDisposablePublisher\u003cint\u003e tickPublisher;\n\n    // Subscriber is used from outside so public property\n    public ISubscriber\u003cint\u003e OnTick { get; }\n\n    public BetterEvent(EventFactory eventFactory)\n    {\n        // CreateEvent can deconstruct by tuple and set together\n        (tickPublisher, OnTick) = eventFactory.CreateEvent\u003cint\u003e();\n\n        // also create async event(IAsyncSubscriber) by `CreateAsyncEvent`\n        // eventFactory.CreateAsyncEvent\n    }\n\n    int count;\n    void Tick()\n    {\n        tickPublisher.Publish(count++);\n    }\n\n    public void Dispose()\n    {\n        // You can unsubscribe all from Publisher.\n        tickPublisher.Dispose();\n    }\n}\n```\n\nIf you want to create event outside of DI, see [Global Provider](#global-provider) section.\n\n```csharp\nIDisposablePublisher\u003cint\u003e tickPublisher;\npublic ISubscriber\u003cint\u003e OnTick { get; }\n\nctor()\n{\n    (tickPublisher, OnTick) = GlobalMessagePipe.CreateEvent\u003cint\u003e();\n}\n```\n\nRequest/Response/All\n---\nSimilar as [MediatR](https://github.com/jbogard/MediatR), implement support of mediator pattern.\n\n```csharp\npublic interface IRequestHandler\u003cin TRequest, out TResponse\u003e\n{\n    TResponse Invoke(TRequest request);\n}\n\npublic interface IAsyncRequestHandler\u003cin TRequest, TResponse\u003e\n{\n    ValueTask\u003cTResponse\u003e InvokeAsync(TRequest request, CancellationToken cancellationToken = default);\n}\n```\n\nFor example, declare handler for Ping type.\n\n```csharp\npublic readonly struct Ping { }\npublic readonly struct Pong { }\n\npublic class PingPongHandler : IRequestHandler\u003cPing, Pong\u003e\n{\n    public Pong Invoke(Ping request)\n    {\n        Console.WriteLine(\"Ping called.\");\n        return new Pong();\n    }\n}\n```\n\nYou can get handler like this.\n\n```csharp\nclass FooController\n{\n    IRequestHandler\u003cPing, Pong\u003e requestHandler;\n\n    // automatically instantiate PingPongHandler.\n    public FooController(IRequestHandler\u003cPing, Pong\u003e requestHandler)\n    {\n        this.requestHandler = requestHandler;\n    }\n\n    public void Run()\n    {\n        var pong = this.requestHandler.Invoke(new Ping());\n        Console.WriteLine(\"PONG\");\n    }\n}\n```\n\nFor more complex implementation patterns, [this Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-application-layer-implementation-web-api#implement-the-command-process-pipeline-with-a-mediator-pattern-mediatr) is applicable.\n\nDeclare many request handlers, you can use `IRequestAllHandler`, `IAsyncRequestAllHandler` instead of single handler.\n\n```csharp\npublic interface IRequestAllHandler\u003cin TRequest, out TResponse\u003e\n{\n    TResponse[] InvokeAll(TRequest request);\n    IEnumerable\u003cTResponse\u003e InvokeAllLazy(TRequest request);\n}\n\npublic interface IAsyncRequestAllHandler\u003cin TRequest, TResponse\u003e\n{\n    ValueTask\u003cTResponse[]\u003e InvokeAllAsync(TRequest request, CancellationToken cancellationToken = default);\n    ValueTask\u003cTResponse[]\u003e InvokeAllAsync(TRequest request, AsyncPublishStrategy publishStrategy, CancellationToken cancellationToken = default);\n    IAsyncEnumerable\u003cTResponse\u003e InvokeAllLazyAsync(TRequest request, CancellationToken cancellationToken = default);\n}\n```\n\n```csharp\npublic class PingPongHandler1 : IRequestHandler\u003cPing, Pong\u003e\n{\n    public Pong Invoke(Ping request)\n    {\n        Console.WriteLine(\"Ping1 called.\");\n        return new Pong();\n    }\n}\n\npublic class PingPongHandler2 : IRequestHandler\u003cPing, Pong\u003e\n{\n    public Pong Invoke(Ping request)\n    {\n        Console.WriteLine(\"Ping1 called.\");\n        return new Pong();\n    }\n}\n\nclass BarController\n{\n    IRequestAllHandler\u003cPing, Pong\u003e requestAllHandler;\n\n    public FooController(IRequestAllHandler\u003cPing, Pong\u003e requestAllHandler)\n    {\n        this.requestAllHandler = requestAllHandler;\n    }\n\n    public void Run()\n    {\n        var pongs = this.requestAllHandler.InvokeAll(new Ping());\n        Console.WriteLine(\"PONG COUNT:\" + pongs.Length);\n    }\n}\n```\n\nSubscribe Extensions\n---\n`ISubscriber`(`IAsyncSubscriber`) interface requires `IMessageHandler\u003cT\u003e` to handle message.\n\n```csharp\npublic interface ISubscriber\u003cTMessage\u003e\n{\n    IDisposable Subscribe(IMessageHandler\u003cTMessage\u003e handler, params MessageHandlerFilter\u003cTMessage\u003e[] filters);\n}\n```\n\nHowever, the extension method allows you to write `Action\u003cT\u003e` directly.\n\n```csharp\npublic static IDisposable Subscribe\u003cTMessage\u003e(this ISubscriber\u003cTMessage\u003e subscriber, Action\u003cTMessage\u003e handler, params MessageHandlerFilter\u003cTMessage\u003e[] filters)\npublic static IDisposable Subscribe\u003cTMessage\u003e(this ISubscriber\u003cTMessage\u003e subscriber, Action\u003cTMessage\u003e handler, Func\u003cTMessage, bool\u003e predicate, params MessageHandlerFilter\u003cTMessage\u003e[] filters)\npublic static IObservable\u003cTMessage\u003e AsObservable\u003cTMessage\u003e(this ISubscriber\u003cTMessage\u003e subscriber, params MessageHandlerFilter\u003cTMessage\u003e[] filters)\npublic static IAsyncEnumerable\u003cTMessage\u003e AsAsyncEnumerable\u003cTMessage\u003e(this IAsyncSubscriber\u003cTMessage\u003e subscriber, params AsyncMessageHandlerFilter\u003cTMessage\u003e[] filters)\npublic static ValueTask\u003cTMessage\u003e FirstAsync\u003cTMessage\u003e(this ISubscriber\u003cTMessage\u003e subscriber, CancellationToken cancellationToken, params MessageHandlerFilter\u003cTMessage\u003e[] filters)\npublic static ValueTask\u003cTMessage\u003e FirstAsync\u003cTMessage\u003e(this ISubscriber\u003cTMessage\u003e subscriber, CancellationToken cancellationToken, Func\u003cTMessage, bool\u003e predicate, params MessageHandlerFilter\u003cTMessage\u003e[] filters)\n```\n\nAlso, the `Func\u003cTMessage, bool\u003e` overload can filter messages by predicate (internally implemented with PredicateFilter, where Order is int.MinValue and is always checked first).\n\n`AsObservable` can convert message pipeline to `IObservable\u003cT\u003e`, it can handle by Reactive Extensions(in Unity, you can use `UniRx`). `AsObservable` exists in sync subscriber(keyless, keyed, buffered).\n\n`AsAsyncEnumerable` can convert message pipeline to `IAsyncEnumerable\u003cT\u003e`, it can handle by async LINQ and async foreach. `AsAsyncEnumerable` exists in async subscriber(keyless, keyed, buffered).\n\n`FirstAsync` gets the first value of message. It is similar as `AsObservable().FirstAsync()`, `AsObservable().Where().FirstAsync()`. If uses `CancellationTokenSource(TimeSpan)` then similar as `AsObservable().Timeout().FirstAsync()`. Argument of `CancellationToken` is required to avoid task leak. \n\n```csharp\n// for Unity, use cts.CancelAfterSlim(TIimeSpan) instead.\nusing var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));\nvar value = await subscriber.FirstAsync(cts.Token);\n```\n\n`FirstAsync` exists in both sync and async subscriber(keyless, keyed, buffered).\n\nFilter\n---\nFilter system can hook before and after method invocation. It is implemented with the Middleware pattern, which allows you to write synchronous and asynchronous code with similar syntax. MessagePipe provides different filter types - sync (`MessageHandlerFilter\u003cT\u003e`), async (`AsyncMessageHandlerFilter\u003cT\u003e`), request (`RequestHandlerFilter\u003cTReq, TRes\u003e`) and async request (`AsyncRequestHandlerFilter\u003cTReq, TRes\u003e`). To implement other concerete filters the above filter types can be extended.\n\nFilters can be specified in three places - global(by `MessagePipeOptions.AddGlobalFilter`), per handler type, and per subscription. These filters are sorted according to the Order specified in each of them, and are generated when subscribing.\n\nSince the filter is generated on a per subscription basis, the filter can have a state.\n\n```csharp\npublic class ChangedValueFilter\u003cT\u003e : MessageHandlerFilter\u003cT\u003e\n{\n    T lastValue;\n\n    public override void Handle(T message, Action\u003cT\u003e next)\n    {\n        if (EqualityComparer\u003cT\u003e.Default.Equals(message, lastValue))\n        {\n            return;\n        }\n\n        lastValue = message;\n        next(message);\n    }\n}\n\n// uses(per subscribe)\nsubscribe.Subscribe(x =\u003e Console.WriteLine(x), new ChangedValueFilter\u003cint\u003e(){ Order = 100 });\n\n// add per handler type(use generics filter, write open generics)\n[MessageHandlerFilter(typeof(ChangedValueFilter\u003c\u003e), 100)]\npublic class WriteLineHandler\u003cT\u003e : IMessageHandler\u003cT\u003e\n{\n    public void Handle(T message) =\u003e Console.WriteLine(message);\n}\n\n// add per global\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        services.AddMessagePipe(options =\u003e\n        {\n            options.AddGlobalMessageHandlerFilter(typeof(ChangedValueFilter\u003c\u003e), 100);\n        });\n    });\n```\n\nuse the filter by attribute, you can use these attributes: `[MessageHandlerFilter(type, order)]`, `[AsyncMessageHandlerFilter(type, order)]`, `[RequestHandlerFilter(type, order)]`, `[AsyncRequestHandlerFilter(type, order)]`.\n\nThese are idea showcase of filter.\n\n```csharp\npublic class PredicateFilter\u003cT\u003e : MessageHandlerFilter\u003cT\u003e\n{\n    private readonly Func\u003cT, bool\u003e predicate;\n\n    public PredicateFilter(Func\u003cT, bool\u003e predicate)\n    {\n        this.predicate = predicate;\n    }\n\n    public override void Handle(T message, Action\u003cT\u003e next)\n    {\n        if (predicate(message))\n        {\n            next(message);\n        }\n    }\n}\n```\n\n```csharp\npublic class LockFilter\u003cT\u003e : MessageHandlerFilter\u003cT\u003e\n{\n    readonly object gate = new object();\n\n    public override void Handle(T message, Action\u003cT\u003e next)\n    {\n        lock (gate)\n        {\n            next(message);\n        }\n    }\n}\n```\n\n```csharp\npublic class IgnoreErrorFilter\u003cT\u003e : MessageHandlerFilter\u003cT\u003e\n{\n    readonly ILogger\u003cIgnoreErrorFilter\u003cT\u003e\u003e logger;\n\n    public IgnoreErrorFilter(ILogger\u003cIgnoreErrorFilter\u003cT\u003e\u003e logger)\n    {\n        this.logger = logger;\n    }\n\n    public override void Handle(T message, Action\u003cT\u003e next)\n    {\n        try\n        {\n            next(message);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, \"\"); // error logged, but do not propagate\n        }\n    }\n}\n```\n\n```csharp\npublic class DispatcherFilter\u003cT\u003e : MessageHandlerFilter\u003cT\u003e\n{\n    readonly Dispatcher dispatcher;\n\n    public DispatcherFilter(Dispatcher dispatcher)\n    {\n        this.dispatcher = dispatcher;\n    }\n\n    public override void Handle(T message, Action\u003cT\u003e next)\n    {\n        dispatcher.BeginInvoke(() =\u003e\n        {\n            next(message);\n        });\n    }\n}\n```\n\n```csharp\npublic class DelayRequestFilter : AsyncRequestHandlerFilter\u003cint, int\u003e\n{\n    public override async ValueTask\u003cint\u003e InvokeAsync(int request, CancellationToken cancellationToken, Func\u003cint, CancellationToken, ValueTask\u003cint\u003e\u003e next)\n    {\n        await Task.Delay(TimeSpan.FromSeconds(request));\n        var response = await next(request, cancellationToken);\n        return response;\n    }\n}\n```\n\nManaging Subscription and Diagnostics\n---\nSubscribe returns `IDisposable`; when call `Dispose` then unsubscribe. A better reason than event is that it is easy to Unsubscribe. To manage multiple IDisposable, you can use `CompositeDisposable` in Rx(UniRx) or `DisposableBag` included in MessagePipe.\n\n```csharp\nIDisposable disposable;\n\nvoid OnInitialize(ISubscriber\u003cint\u003e subscriber)\n{\n    var d1 = subscriber.Subscribe(_ =\u003e { });\n    var d2 = subscriber.Subscribe(_ =\u003e { });\n    var d3 = subscriber.Subscribe(_ =\u003e { });\n\n    // static DisposableBag: DisposableBag.Create(1~7(optimized) or N);\n    disposable = DisposableBag.Create(d1, d2, d3);\n}\n\nvoid Close()\n{\n    // dispose all subscription\n    disposable?.Dispose();\n}\n```\n\n```csharp\nIDisposable disposable;\n\nvoid OnInitialize(ISubscriber\u003cint\u003e subscriber)\n{\n    // use builder pattern, you can use subscription.AddTo(bag)\n    var bag = DisposableBag.CreateBuilder();\n\n    subscriber.Subscribe(_ =\u003e { }).AddTo(bag);\n    subscriber.Subscribe(_ =\u003e { }).AddTo(bag);\n    subscriber.Subscribe(_ =\u003e { }).AddTo(bag);\n\n    disposable = bag.Build(); // create final composite IDisposable\n}\n\nvoid Close()\n{\n    // dispose all subscription\n    disposable?.Dispose();\n}\n```\n\n```csharp\nIDisposable disposable;\n\nvoid OnInitialize(ISubscriber\u003cint\u003e subscriber)\n{\n    var bag = DisposableBag.CreateBuilder();\n\n    // calling once(or x count), you can use DisposableBag.CreateSingleAssignment to hold subscription reference.\n    var d = DisposableBag.CreateSingleAssignment();\n    \n    // you can invoke Dispose in handler action.\n    // assign disposable, you can use `SetTo` and `AddTo` bag.\n    // or you can use d.Disposable = subscriber.Subscribe();\n    subscriber.Subscribe(_ =\u003e { d.Dispose(); }).SetTo(d).AddTo(bag);\n\n    disposable = bag.Build();\n}\n\nvoid Close()\n{\n    disposable?.Dispose();\n}\n```\n\nThe returned `IDisposable` value **must** be handled. If it is ignored, it will leak. However Weak reference, which is widely used in WPF, is an anti-pattern. All subscriptions should be managed explicitly.\n\nYou can monitor subscription count by `MessagePipeDiagnosticsInfo`. It can get from service provider(or DI).\n\n```csharp\npublic sealed class MessagePipeDiagnosticsInfo\n{\n    /// \u003csummary\u003eGet current subscribed count.\u003c/summary\u003e\n    public int SubscribeCount { get; }\n\n    /// \u003csummary\u003e\n    /// When MessagePipeOptions.EnableCaptureStackTrace is enabled, list all stacktrace on subscribe.\n    /// \u003c/summary\u003e\n    public StackTraceInfo[] GetCapturedStackTraces(bool ascending = true);\n\n    /// \u003csummary\u003e\n    /// When MessagePipeOptions.EnableCaptureStackTrace is enabled, groped by caller of subscribe.\n    /// \u003c/summary\u003e\n    public ILookup\u003cstring, StackTraceInfo\u003e GetGroupedByCaller(bool ascending = true)\n}\n```\n\nIf you monitor SubscribeCount, you can check leak of subscription.\n\n```csharp\npublic class MonitorTimer : IDisposable\n{\n    CancellationTokenSource cts = new CancellationTokenSource();\n\n    public MonitorTimer(MessagePipeDiagnosticsInfo diagnosticsInfo)\n    {\n        RunTimer(diagnosticsInfo);\n    }\n\n    async void RunTimer(MessagePipeDiagnosticsInfo diagnosticsInfo)\n    {\n        while (!cts.IsCancellationRequested)\n        {\n            // show SubscribeCount\n            Console.WriteLine(\"SubscribeCount:\" + diagnosticsInfo.SubscribeCount);\n            await Task.Delay(TimeSpan.FromSeconds(5), cts.Token);\n        }\n    }\n\n    public void Dispose()\n    {\n        cts.Cancel();\n    }\n}\n```\n\nAlso, by enabling MessagePipeOptions.EnableCaptureStackTrace (disabled by default), the location of the subscribed location can be displayed, making it easier to find the location of the leak if it exists.\n\nCheck the Count of GroupedByCaller, and if any of them show abnormal values, then the stack trace is where it occurs, and you probably ignore Subscription.\n\nfor Unity, `Window -\u003e  MessagePipe Diagnostics` window is useful for monitoring subscritpion. It visualizes `MessagePipeDianogsticsInfo`.\n\n![image](https://user-images.githubusercontent.com/46207/116953319-e2e41580-acc7-11eb-88c9-a4704bf3e3c9.png)\n\nTo Enable use of the MessagePipeDiagnostics window, require to set up `GlobalMessagePipe`.\n\n```csharp\n// VContainer\npublic class MessagePipeDemo : VContainer.Unity.IStartable\n{\n    public MessagePipeDemo(IObjectResolver resolver)\n    {\n        // require this line.\n        GlobalMessagePipe.SetProvider(resolver.AsServiceProvider());\n    }\n}\n\n// Zenject\nvoid Configure(DiContainer container)\n{\n    GlobalMessagePipe.SetProvider(container.AsServiceProvider());\n}\n\n// builtin\nvar prodiver = builder.BuildServiceProvider();\nGlobalMessagePipe.SetProvider(provider);\n```\n\nAnalyzer\n---\nIn previous section, we anounce `The returned IDisposable value **must** be handled`. To prevent subscription leak, we provide roslyn analyzer.\n\n\u003e PM\u003e Install-Package [MessagePipe.Analyzer](https://www.nuget.org/packages/MessagePipe.Analyzer)\n\n![](https://user-images.githubusercontent.com/46207/117535259-da753d00-b02f-11eb-9818-0ab5ef3049b1.png)\n\nThis will raise an error for unhandled `Subscribe`.\n\nThis analyzer can use after Unity 2020.2(see: [Roslyn analyzers and ruleset files](https://docs.unity3d.com/2020.2/Documentation/Manual/roslyn-analyzers.html) document). `MessagePipe.Analyzer.dll` exists in [releases page](https://github.com/Cysharp/MessagePipe/releases/).\n\n![](https://user-images.githubusercontent.com/46207/117535248-d5b08900-b02f-11eb-8add-33101a71033a.png)\n\nCurrently Unity's analyzer support is incomplete. We are complementing analyzer support with editor extension, please check the [Cysharp/CsprojModifier](https://github.com/Cysharp/CsprojModifier).\n\n![](https://github.com/Cysharp/CsprojModifier/raw/master/docs/images/Screen-01.png)\n\nIDistributedPubSub / MessagePipe.Redis\n---\nFor the distributed(networked) Pub/Sub, you can use `IDistributedPublisher\u003cTKey, TMessage\u003e`, `IDistributedSubscriber\u003cTKey, TMessage\u003e` instead of `IAsyncPublisher`.\n\n```csharp\npublic interface IDistributedPublisher\u003cTKey, TMessage\u003e\n{\n    ValueTask PublishAsync(TKey key, TMessage message, CancellationToken cancellationToken = default);\n}\n\npublic interface IDistributedSubscriber\u003cTKey, TMessage\u003e\n{\n    // and also without filter overload.\n    public ValueTask\u003cIAsyncDisposable\u003e SubscribeAsync(TKey key, IMessageHandler\u003cTMessage\u003e handler, MessageHandlerFilter\u003cTMessage\u003e[] filters, CancellationToken cancellationToken = default);\n    public ValueTask\u003cIAsyncDisposable\u003e SubscribeAsync(TKey key, IAsyncMessageHandler\u003cTMessage\u003e handler, AsyncMessageHandlerFilter\u003cTMessage\u003e[] filters, CancellationToken cancellationToken = default);\n}\n```\n\n`IAsyncPublisher` means in-memory Pub/Sub. Since processing over the network is fundamentally different, you need to use a different interface to avoid confusion.\n\nRedis is available as a standard network provider.\n\n\u003e PM\u003e Install-Package [MessagePipe.Redis](https://www.nuget.org/packages/MessagePipe.Redis)\n\nuse `AddMessagePipeRedis` to enable redis provider.\n\n```csharp\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        services.AddMessagePipe()\n            .AddRedis(IConnectionMultiplexer | IConnectionMultiplexerFactory, configure);\n    })\n```\n\n`IConnectionMultiplexer` overload, you can pass [StackExchange.Redis](https://github.com/StackExchange/StackExchange.Redis)'s `ConnectionMultiplexer` directly. Implement own `IConnectionMultiplexerFactory` to allow for per-key distribution and use from connection pools.\n\n`MessagePipeRedisOptions`, you can configure serialization.\n\n```csharp\npublic sealed class MessagePipeRedisOptions\n{\n    public IRedisSerializer RedisSerializer { get; set; }\n}\n\npublic interface IRedisSerializer\n{\n    byte[] Serialize\u003cT\u003e(T value);\n    T Deserialize\u003cT\u003e(byte[] value);\n}\n```\n\nIn default uses [MessagePack for C#](https://github.com/neuecc/MessagePack-CSharp)'s `ContractlessStandardResolver`. You can change to use other `MessagePackSerializerOptions` by `new MessagePackRedisSerializer(options)` or implement own serializer wrapper.\n\nMessagePipe has in-memory IDistributedPublisher/Subscriber for local test usage.\n\n```csharp\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        var config = ctx.Configuration.Get\u003cMyConfig\u003e();\n\n        var builder = services.AddMessagePipe();\n        if (config.IsLocal)\n        {\n            // use in-memory IDistributedPublisher/Subscriber in local.\n            builder.AddInMemoryDistributedMessageBroker();   \n        }\n        else\n        {\n            // use Redis IDistributedPublisher/Subscriber\n            builder.AddRedis();\n        }\n    });\n```\n\nInterprocessPubSub, IRemoteAsyncRequest / MessagePipe.Interprocess\n---\nFor the interprocess(NamedPipe/UDP/TCP) Pub/Sub(IPC), you can use `IDistributedPublisher\u003cTKey, TMessage\u003e`, `IDistributedSubscriber\u003cTKey, TMessage\u003e` similar as `MessagePipe.Redis`.\n\n\u003e PM\u003e Install-Package MessagePipe.Interprocess\n\nMessagePipe.Interprocess is also exsits on Unity(except NamedPipe).\n\nuse `AddUdpInterprocess`, `AddTcpInterprocess`, `AddNamedPipeInterprocess`, `AddUdpInterprocessUds`, `AddTcpInterprocessUds` to enable interprocess provider(Uds is Unix domain socket, most performant option).\n\n```csharp\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        services.AddMessagePipe()\n            .AddUdpInterprocess(\"127.0.0.1\", 3215, configure); // setup host and port.\n            // .AddTcpInterprocess(\"127.0.0.1\", 3215, configure);\n            // .AddNamedPipeInterprocess(\"messagepipe-namedpipe\", configure);\n            // .AddUdpInterprocessUds(\"domainSocketPath\")\n            // .AddTcpInterprocessUds(\"domainSocketPath\")\n    })\n```\n\n```csharp\npublic async P(IDistributedPublisher\u003cstring, int\u003e publisher)\n{\n    // publish value to remote process.\n    await publisher.PublishAsync(\"foobar\", 100);\n}\n\npublic async S(IDistributedSubscriber\u003cstring, int\u003e subscriber)\n{\n    // subscribe remote-message with \"foobar\" key.\n    await subscriber.SubscribeAsync(\"foobar\", x =\u003e\n    {\n        Console.WriteLine(x);\n    });\n}\n```\n\nwhen injected `IDistributedPublisher`, process will be `server`, start to listen client. when injected `IDistributedSubscriber`, process will be `client`, start to connect to server. when DI scope is closed, server/client connection is closed.\n\nUdp is connectionless protocol so does not require server is started before client connect. However protocol limitation, does not send over 64K message. We're recommend to use this if message is not large.\n\nNamedpipe is 1:1 connection, can not connect multiple subscribers.\n\nTcp has no such restrictions and is the most flexible of all the options.\n\nIn default uses [MessagePack for C#](https://github.com/neuecc/MessagePack-CSharp)'s `ContractlessStandardResolver` for message serialization. You can change to use other `MessagePackSerializerOptions` by MessagePipeInterprocessOptions.MessagePackSerializerOptions.\n\n```csharp\nbuilder.AddUdpInterprocess(\"127.0.0.1\", 3215, options =\u003e\n{\n    // You can configure other options, `InstanceLifetime` and `UnhandledErrorHandler`.\n    options.MessagePackSerializerOptions = StandardResolver.Options;\n});\n```\n\nFor IPC-RPC, you can use `IRemoteRequestHandler\u003cin TRequest, TResponse\u003e` that invoke remote `IAsyncRequestHandler\u003cTRequest, TResponse\u003e`. using `TcpInterprocess` or `NamedPipeInterprocess` enabled it.\n\n```csharp\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        services.AddMessagePipe()\n            .AddTcpInterprocess(\"127.0.0.1\", 3215, x =\u003e\n            {\n                x.HostAsServer = true; // if remote process as server, set true(otherwise false(default)).\n            });\n    });\n```\n\n```csharp\n// example: server handler\npublic class MyAsyncHandler : IAsyncRequestHandler\u003cint, string\u003e\n{\n    public async ValueTask\u003cstring\u003e InvokeAsync(int request, CancellationToken cancellationToken = default)\n    {\n        await Task.Delay(1);\n        if (request == -1)\n        {\n            throw new Exception(\"NO -1\");\n        }\n        else\n        {\n            return \"ECHO:\" + request.ToString();\n        }\n    }\n}\n```\n\n```csharp\n// client\nasync void A(IRemoteRequestHandler\u003cint, string\u003e remoteHandler)\n{\n    var v = await remoteHandler.InvokeAsync(9999);\n    Console.WriteLine(v); // ECHO:9999\n}\n```\n\nFor Unity, requires to import MessagePack-CSharp package and needs slightly different configuration.\n\n```csharp\n// example of VContainer\nvar builder = new ContainerBuilder();\nvar options = builder.RegisterMessagePipe(configure);\n\nvar messagePipeBuilder = builder.ToMessagePipeBuilder(); // require to convert ServiceCollection to enable Intereprocess\n\nvar interprocessOptions = messagePipeBuilder.AddTcpInterprocess();\n\n// register manually.\n// IDistributedPublisher/Subscriber\nmessagePipeBuilder.RegisterTcpInterprocessMessageBroker\u003cint, int\u003e(interprocessOptions);\n// RemoteHandler\nbuilder.RegisterAsyncRequestHandler\u003cint, string, MyAsyncHandler\u003e(options); // for server\nmessagePipeBuilder.RegisterTcpRemoteRequestHandler\u003cint, string\u003e(interprocessOptions); // for client\n```\n\nMessagePipeOptions\n---\nYou can configure MessagePipe behaviour by `MessagePipeOptions` in `AddMessagePipe(Action\u003cMMessagePipeOptions\u003e configure)`.\n\n```csharp\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        // var config = ctx.Configuration.Get\u003cMyConfig\u003e(); // optional: get settings from configuration(use it for options configure)\n\n        services.AddMessagePipe(options =\u003e\n        {\n            options.InstanceLifetime = InstanceLifetime.Scoped;\n#if DEBUG\n            // EnableCaptureStackTrace slows performance, so recommended to use only in DEBUG and in profiling, disable it.\n            options.EnableCaptureStackTrace = true;\n#endif\n        });\n    })\n```\n\nOption has these properties(and method).\n\n```csharp\npublic sealed class MessagePipeOptions\n{\n    AsyncPublishStrategy DefaultAsyncPublishStrategy; // default is Parallel\n    HandlingSubscribeDisposedPolicy HandlingSubscribeDisposedPolic; // default is Ignore\n    InstanceLifetime InstanceLifetime; // default is Singleton\n    InstanceLifetime RequestHandlerLifetime; // default is Scoped\n    bool EnableAutoRegistration;  // default is true\n    bool EnableCaptureStackTrace; // default is false\n\n    void SetAutoRegistrationSearchAssemblies(params Assembly[] assemblies);\n    void SetAutoRegistrationSearchTypes(params Type[] types);\n    void AddGlobal***Filter\u003cT\u003e();\n}\n\npublic enum AsyncPublishStrategy\n{\n    Parallel, Sequential\n}\n\npublic enum InstanceLifetime\n{\n    Singleton, Scoped, Transient\n}\n\npublic enum HandlingSubscribeDisposedPolicy\n{\n    Ignore, Throw\n}\n```\n\n### DefaultAsyncPublishStrategy\n\n`IAsyncPublisher` has `PublishAsync` method. If AsyncPublishStrategy.Sequential, await each subscribers. If Parallel, uses WhenAll.\n\n```csharp\npublic interface IAsyncPublisher\u003cTMessage\u003e\n{\n    // using Default AsyncPublishStrategy\n    ValueTask PublishAsync(TMessage message, CancellationToken cancellationToken = default);\n    ValueTask PublishAsync(TMessage message, AsyncPublishStrategy publishStrategy, CancellationToken cancellationToken = default);\n    // snip others...\n}\n\npublic interface IAsyncPublisher\u003cTKey, TMessage\u003e\n    where TKey : notnull\n{\n    // using Default AsyncPublishStrategy\n    ValueTask PublishAsync(TKey key, TMessage message, CancellationToken cancellationToken = default);\n    ValueTask PublishAsync(TKey key, TMessage message, AsyncPublishStrategy publishStrategy, CancellationToken cancellationToken = default);\n    // snip others...\n}\n\npublic interface IAsyncRequestAllHandler\u003cin TRequest, TResponse\u003e\n{\n    // using Default AsyncPublishStrategy\n    ValueTask\u003cTResponse[]\u003e InvokeAllAsync(TRequest request, CancellationToken cancellationToken = default);\n    ValueTask\u003cTResponse[]\u003e InvokeAllAsync(TRequest request, AsyncPublishStrategy publishStrategy, CancellationToken cancellationToken = default);\n    // snip others...\n}\n```\n\n`MessagePipeOptions.DefaultAsyncPublishStrategy`'s default is `Parallel`.\n\n### HandlingSubscribeDisposedPolicy\n\nWhen `ISubscriber.Subscribe` after MessageBroker(publisher/subscriber manager) is disposed(for example, scope is disposed), choose `Ignore`(returns empty `IDisposable`) or `Throw` exception. Default is `Ignore`.\n\n### InstanceLifetime\n\nConfigure MessageBroker(publisher/subscriber manager)'s lifetime of DI cotainer. You can choose `Singleton` or `Scoped`. Default is `Singleton`. When choose `Scoped`, each messagebrokers manage different subscribers and when scope is disposed, unsubscribe all managing subscribers.\n\n### RequestHandlerLifetime\n\nConfigure IRequestHandler/IAsyncRequestHandler's lifetime of DI container. You can choose `Singleton` or `Scoped` or `Transient`. Default is `Scoped`.\n\n### EnableAutoRegistration/SetAutoRegistrationSearchAssemblies/SetAutoRegistrationSearchTypes\n\nRegister `IRequestHandler`, `IAsyncHandler` and filters to DI container automatically on startup. Default is `true` and default search target is CurrentDomain's all assemblies and types. However, this sometimes fails to detect the assembly being stripped. In that case, you can enable the search by explicitly adding it to `SetAutoRegistrationSearchAssemblies` or `SetAutoRegistrationSearchTypes`.\n\n`[IgnoreAutoRegistration]` attribute allows to disable auto registration which attribute attached.\n\n### EnableCaptureStackTrace\n\nSee the details [Managing Subscription and Diagnostics](#managing-subscription-and-diagnostics) section, if `true` then capture stacktrace on Subscribe. It is useful for debugging but performance will be degraded. Default is `false` and recommended to enable only debug.\n\n### AddGlobal***Filter\n\nAdd global filter, for example logging filter will be useful.\n\n```csharp\npublic class LoggingFilter\u003cT\u003e : MessageHandlerFilter\u003cT\u003e\n{\n    readonly ILogger\u003cLoggingFilter\u003cT\u003e\u003e logger;\n\n    public LoggingFilter(ILogger\u003cLoggingFilter\u003cT\u003e\u003e logger)\n    {\n        this.logger = logger;\n    }\n\n    public override void Handle(T message, Action\u003cT\u003e next)\n    {\n        try\n        {\n            logger.LogDebug(\"before invoke.\");\n            next(message);\n            logger.LogDebug(\"invoke completed.\");\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, \"error\");\n        }\n    }\n}\n```\n\nTo enable all types, use open generics.\n\n```csharp\nHost.CreateDefaultBuilder()\n    .ConfigureServices((ctx, services) =\u003e\n    {\n        services.AddMessagePipe(options =\u003e\n        {\n            // use typeof(Filter\u003c\u003e, order);\n            options.AddGlobalMessageHandlerFilter(typeof(LoggingFilter\u003c\u003e), -10000);\n        });\n    });\n```\n\nGlobal provider\n---\nIf you want to get publisher/subscriber/handler from globally scope, get `IServiceProvider` before run and set to static helper called `GlobalMessagePipe`.\n\n```csharp\nvar host = Host.CreateDefaultBuilder()\n    .ConfigureServices((ctx, x) =\u003e\n    {\n        x.AddMessagePipe();\n    })\n    .Build(); // build host before run.\n\nGlobalMessagePipe.SetProvider(host.Services); // set service provider\n\nawait host.RunAsync(); // run framework.\n```\n\n`GlobalMessagePipe` has these static method(`GetPublisher\u003cT\u003e`, `GetSubscriber\u003cT\u003e`, `CreateEvent\u003cT\u003e`, etc...) so you can get globally.\n\n![image](https://user-images.githubusercontent.com/46207/116521078-7c00de00-a90e-11eb-85c0-2c62c140c51d.png)\n\nIntegration with other DI library\n---\nAll(popular) DI libraries has `Microsoft.Extensions.DependencyInjection` bridge so configure by MS.E.DI and use bridge if you want.\n\nCompare with Channels\n---\n[System.Threading.Channels](https://docs.microsoft.com/en-us/dotnet/api/system.threading.channels)(for Unity, `UniTask.Channels`) uses Queue internal, the producer is not affected by the performance of the consumer, and the consumer can control the flow rate(back pressure). This is a different use than MessagePipe's Pub/Sub.\n\nUnity\n---\nYou need to install Core library and choose [VContainer](https://github.com/hadashiA/VContainer/) or [Zenject](https://github.com/modesttree/Zenject) or `BuiltinContainerBuilder` for runtime. You can install via UPM git URL package or asset package(MessagePipe.*.unitypackage) available in [MessagePipe/releases](https://github.com/Cysharp/MessagePipe/releases) page.\n\n* Core `https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe`\n* VContainer `https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe.VContainer`\n* Zenject `https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe.Zenject`\n\nAndalso, requires [UniTask](https://github.com/Cysharp/UniTask) to install, all `ValueTask` declaration in .NET is replaced to `UniTask`.\n\n\u003e [!NOTE]\n\u003e Unity version does not have open generics support(for IL2CPP) and does not support auto registration. Therefore, all required types need to be manually registered.\n\nVContainer's installation sample.\n\n```csharp\npublic class GameLifetimeScope : LifetimeScope\n{\n    protected override void Configure(IContainerBuilder builder)\n    {\n        // RegisterMessagePipe returns options.\n        var options = builder.RegisterMessagePipe(/* configure option */);\n        \n        // Setup GlobalMessagePipe to enable diagnostics window and global function\n        builder.RegisterBuildCallback(c =\u003e GlobalMessagePipe.SetProvider(c.AsServiceProvider()));\n\n        // RegisterMessageBroker: Register for IPublisher\u003cT\u003e/ISubscriber\u003cT\u003e, includes async and buffered.\n        builder.RegisterMessageBroker\u003cint\u003e(options);\n\n        // also exists RegisterMessageBroker\u003cTKey, TMessage\u003e, RegisterRequestHandler, RegisterAsyncRequestHandler\n\n        // RegisterMessageHandlerFilter: Register for filter, also exists RegisterAsyncMessageHandlerFilter, Register(Async)RequestHandlerFilter\n        builder.RegisterMessageHandlerFilter\u003cMyFilter\u003cint\u003e\u003e();\n\n        builder.RegisterEntryPoint\u003cMessagePipeDemo\u003e(Lifetime.Singleton);\n    }\n}\n\npublic class MessagePipeDemo : VContainer.Unity.IStartable\n{\n    readonly IPublisher\u003cint\u003e publisher;\n    readonly ISubscriber\u003cint\u003e subscriber;\n\n    public MessagePipeDemo(IPublisher\u003cint\u003e publisher, ISubscriber\u003cint\u003e subscriber)\n    {\n        this.publisher = publisher;\n        this.subscriber = subscriber;\n    }\n\n    public void Start()\n    {\n        var d = DisposableBag.CreateBuilder();\n        subscriber.Subscribe(x =\u003e Debug.Log(\"S1:\" + x)).AddTo(d);\n        subscriber.Subscribe(x =\u003e Debug.Log(\"S2:\" + x)).AddTo(d);\n\n        publisher.Publish(10);\n        publisher.Publish(20);\n        publisher.Publish(30);\n\n        var disposable = d.Build();\n        disposable.Dispose();\n    }\n}\n```\n\n\u003e [!TIP]\n\u003e If you are using Unity 2022.1 or later and VContainer 1.14.0 or later, you do not need `RegsiterMessageBroker\u003c\u003e`. \n\u003e A set of types including `ISubscriber\u003c\u003e`, `IPublisher\u003c\u003e` or its asynchronous version will be resolved automatically.\n\u003e Note that `IRequesthandler\u003c\u003e` and `IRequestAllHanlder\u003c\u003e` still require manual registration.\n\n\nUnity version does not have open generics support(for IL2CPP) and does not support auto registration. Therefore, all required types need to be manually registered.\n\n\nZenject's installation sample.\n\n```csharp\nvoid Configure(DiContainer builder)\n{\n    // BindMessagePipe returns options.\n    var options = builder.BindMessagePipe(/* configure option */);\n    \n    // BindMessageBroker: Register for IPublisher\u003cT\u003e/ISubscriber\u003cT\u003e, includes async and buffered.\n    builder.BindMessageBroker\u003cint\u003e(options);\n\n    // also exists BindMessageBroker\u003cTKey, TMessage\u003e, BindRequestHandler, BindAsyncRequestHandler\n\n    // BindMessageHandlerFilter: Bind for filter, also exists BindAsyncMessageHandlerFilter, Bind(Async)RequestHandlerFilter\n    builder.BindMessageHandlerFilter\u003cMyFilter\u003cint\u003e\u003e();\n\n    // set global to enable diagnostics window and global function\n    GlobalMessagePipe.SetProvider(builder.AsServiceProvider());\n}\n```\n\n\u003e Zenject version is not supported `InstanceScope.Singleton` for Zenject's limitation. The default is `Scoped`, which cannot be changed.\n\n`BuiltinContainerBuilder` is builtin minimum DI library for MessagePipe, it no needs other DI library to use MessagePipe. Here is installation sample.\n\n```csharp\nvar builder = new BuiltinContainerBuilder();\n\nbuilder.AddMessagePipe(/* configure option */);\n\n// AddMessageBroker: Register for IPublisher\u003cT\u003e/ISubscriber\u003cT\u003e, includes async and buffered.\nbuilder.AddMessageBroker\u003cint\u003e(options);\n\n// also exists AddMessageBroker\u003cTKey, TMessage\u003e, AddRequestHandler, AddAsyncRequestHandler\n\n// AddMessageHandlerFilter: Register for filter, also exists RegisterAsyncMessageHandlerFilter, Register(Async)RequestHandlerFilter\nbuilder.AddMessageHandlerFilter\u003cMyFilter\u003cint\u003e\u003e();\n\n// create provider and set to Global(to enable diagnostics window and global fucntion)\nvar provider = builder.BuildServiceProvider();\nGlobalMessagePipe.SetProvider(provider);\n\n// --- to use MessagePipe, you can use from GlobalMessagePipe.\nvar p = GlobalMessagePipe.GetPublisher\u003cint\u003e();\nvar s = GlobalMessagePipe.GetSubscriber\u003cint\u003e();\n\nvar d = s.Subscribe(x =\u003e Debug.Log(x));\n\np.Publish(10);\np.Publish(20);\np.Publish(30);\n\nd.Dispose();\n```\n\n\u003e BuiltinContainerBuilder does not supports scope(always `InstanceScope.Singleton`), `IRequestAllHandler/IAsyncRequestAllHandler`, and many DI functionally, so we recommend to use by `GlobalMessagePipe` when use BuiltinContainerBuilder.\n\nAdding global filter, you can not use open generics filter so recommended to create these helper method.\n\n```csharp\n// Register IPublisher\u003cT\u003e/ISubscriber\u003cT\u003e and global filter.\nstatic void RegisterMessageBroker\u003cT\u003e(IContainerBuilder builder, MessagePipeOptions options)\n{\n    builder.RegisterMessageBroker\u003cT\u003e(options);\n\n    // setup for global filters.\n    options.AddGlobalMessageHandlerFilter\u003cMyMessageHandlerFilter\u003cT\u003e\u003e();\n}\n\n// Register IRequestHandler\u003cTReq, TRes\u003e/IRequestAllHandler\u003cTReq, TRes\u003e and global filter.\nstatic void RegisterRequest\u003cTRequest, TResponse, THandler\u003e(IContainerBuilder builder, MessagePipeOptions options)\n    where THandler : IRequestHandler\n{\n    builder.RegisterRequestHandler\u003cTRequest, TResponse, THandler\u003e(options);\n    \n    // setup for global filters.\n    options.AddGlobalRequestHandlerFilter\u003cMyRequestHandlerFilter\u003cTRequest, TResponse\u003e\u003e();\n}\n```\n\nAlso you can use `GlobalMessagePipe` and `MessagePipe Diagnostics` window. see: [Global provider](#global-provider) and [Managing Subscription and Diagnostics](#managing-subscription-and-diagnostics) section.\n\nLicense\n---\nThis library is licensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCysharp%2FMessagePipe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCysharp%2FMessagePipe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCysharp%2FMessagePipe/lists"}