C# Channel 簡單實現消息隊列的發佈、訂閱

十年河東,十年河西,莫欺少年窮

學無止境,精益求精

這裏需要聲明的是:

雖說 Channel 類似於 MQ ,但 Channel 並不支持跨項目使用,MQ 作爲中間件,它裏面的消息可以被多個項目共享,但  Channel 中的消息只能用於單體項目中共享。

介紹

首先,Channel本質上是.net中的一種新的集合類型,它與現有的Queue<T>類型非常相似,當然也有不同之處。

System.Threading.Channels 是.NET Core 3.0 後推出的新的集合類型, 具有異步API,高性能,線程安全等特點,它可以用來做消息隊列,進行數據的生產和消費, 公開的 WriterReader api對應消息的生產者和消費者,也讓Channel更加的簡潔和易用,與Rabbit MQ 等其他隊列不同的是,Channel 是進程內的隊列。

爲什麼要使用 Channels

可以利用 Channels 來實現 生產者和消費者 之間的解耦,大體上有兩個好處:

  • 生產者 和 消費者 是相互獨立的,兩者可以並行執行。

  • 如果生產者不給力,可以創建多個的生產者,如果消費者不給力,可以創建更多的消費者。

總的來說,在 生產者-消費者 模式下可以幫助我們提高應用程序的吞吐率。

創建Channel 

nuget 中搜索 Channel 並安裝

channel 分爲固定容量的,和無限容量的

無限容量的Channel創建

            //無限容量的
            var channel_1 = Channel.CreateUnbounded<msgDto>();

            var channel_2 = Channel.CreateUnbounded<string>(new UnboundedChannelOptions()
            {
                SingleWriter = false, //允許一次寫入多個消息
                SingleReader = true //一次只能讀取一條消息
            });

CreateUnbounded<T> 是指消息的類型,可以爲簡單的 int 、string 類型,也可以定義成一個類,上述的 msgDto 就是一個簡答的類

    public class msgDto
    {
        public string msgid { get; set; }
        public string msg { get; set; }
    }

有限容量的Channel創建

            //限容Channel  
            var channel_3 = Channel.CreateBounded<string>(10000);
            var channel_4 = Channel.CreateBounded<msgDto>(new BoundedChannelOptions(100)
            {
                FullMode = BoundedChannelFullMode.Wait, //消息滿了 等待消費 新的消息不插入
                SingleWriter = false,//允許一次寫入多條數據
                SingleReader = false,//允許一次讀取多條數據

            });

注意,BoundedChannelFullMode 共有四種

  • Wait                   等待空間可用以便完成寫入操作。

  • DropWrite          刪除要寫入的項。

  • DropNewest      刪除並忽略通道中的最新項,以便爲要寫入的項留出空間。

  • DropOldest        刪除並忽略通道中的最舊項,以便爲要寫入的項留出空間。

生產/消費數據

        static async Task Main(string[] args)
        {
            //Channel 屬於線程安全的集合
            var channel_1 = Channel.CreateUnbounded<string>(new UnboundedChannelOptions()
            {
                SingleWriter = false, //允許一次寫入多條數據,下面的Parallel.For屬於多線程寫入
                SingleReader = false
            });
            //多線程寫入 
            Parallel.For(0, 10, i =>
            {
                channel_1.Writer.WriteAsync ("hello_" + i);

            });

            //消費數據
            while (await channel_1.Reader.WaitToReadAsync())
            {
                if (channel_1.Reader.TryRead(out var message))
                {
                    Console.WriteLine(message);
                }
            }

            Console.Read();
        }

WaitToReadAsync是一個非阻塞等待,在有消息可讀或Channel關閉時,纔會喚醒並繼續。

考慮到有多個消費者的情況,有可能別的線程已經進行了讀取,這兒使用TryRead進行讀取操作。

要注意:數據的同步工作是由Channel進行管理的。Channel會確保多個消費者不會讀到相同的數據。Channel同時也管理數據的次序。

上述例子的輸出結果:

 因爲是異步多線程寫入,因此,不保證順序,但讀取時,還是按照寫入的順序進行讀取的。

參考:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.channels.boundedchannelfullmode?view=net-6.0

@天才臥龍的波爾卡 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章