簡體   English   中英

使用BlockingCollection縮放連接<T>()

[英]Scaling Connections with BlockingCollection<T>()

我有一台服務器通過TCP LAN與50個或更多設備通信。 每個套接字讀取消息循環都有一個Task.Run。

我將每個消息覆蓋緩沖到一個阻塞隊列中,其中每個阻塞隊列都有一個使用BlockingCollection.Take()的Task.Run。

所以像(半偽代碼):

套接字閱讀任務

Task.Run(() =>
{
    while (notCancelled)
    {
        element = ReadXml();
        switch (element)
        {
            case messageheader:
                MessageBlockingQueue.Add(deserialze<messageType>());
            ...
        }
    }
});

消息緩沖區任務

Task.Run(() =>
{
    while (notCancelled)
    {
        Process(MessageQueue.Take());
    }
});

這將使50多個閱讀任務和50多個任務在他們自己的緩沖區上阻塞。

我這樣做是為了避免阻塞讀取循環並允許程序更公平地分配處理時間,或者我相信。

這是一種處理它的低效方法嗎? 什么是更好的方式?

您可能對“渠道”工作感興趣,特別是: System.Threading.Channels 這樣做的目的是提供異步生產者/消費者隊列,涵蓋單個和多個生產者和消費者場景,上限等。通過使用異步API,您不會占用大量線程只是等待某事做。

您的讀取循環將變為:

while (notCancelled) {
    var next = await queue.Reader.ReadAsync(optionalCancellationToken);
    Process(next);
}

和制片人:

switch (element)
{
    case messageheader:
        queue.Writer.TryWrite(deserialze<messageType>());
        ...
}

所以:微小的變化


或者 - 或者組合 - 你可以查看諸如“管道”之類的東西( https://www.nuget.org/packages/System.IO.Pipelines/ ) - 因為你正在處理TCP數據,這將是一個理想的選擇。適合,這是我在Stack Overflow(處理大量連接)的自定義Web套接字服務器上看到的東西。 由於API始終是異步的,因此它可以很好地平衡工作 - 並且管道API在設計時考慮了典型的TCP場景,例如在檢測幀邊界時部分消耗傳入的數據流。 我已經寫了很多這個用法,代碼示例主要在這里 請注意,“管道”不包括直接TCP層,但“紅寶石”服務器包括一個或第三方庫https://www.nuget.org/packages/Pipelines.Sockets.Unofficial/ (披露) : 我寫的)。

我實際上在另一個項目中做了類似的事情 我學到或將做的不同之處如下:

  1. 首先,最好使用專用線程進行讀/寫循環(使用new Thread(ParameterizedThreadStart) ),因為Task.Run使用池線程,當你在(幾乎)無限循環中使用它時,線程實際上永遠不會返回到游泳池。

     var thread = new Thread(ReaderLoop) { Name = nameof(ReaderLoop) }; // priority, etc if needed thread.Start(cancellationToken); 
  2. 您的Process可以是一個事件,您可以異步調用該事件,以便您的閱讀器循環可以立即返回以盡快處理新的傳入包:

     private void ReaderLoop(object state) { var token = (CancellationToken)state; while (!token.IsCancellationRequested) { try { var message = MessageQueue.Take(token); OnMessageReceived(new MessageReceivedEventArgs(message)); } catch (OperationCanceledException) { if (!disposed && IsRunning) Stop(); break; } } } 

請注意,如果委托有多個目標,那么異步調用並不簡單。 我創建了這個擴展方法來調用池線程上的委托:

public static void InvokeAsync<TEventArgs>(this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs args)
{
    void Callback(IAsyncResult ar)
    {
        var method = (EventHandler<TEventArgs>)ar.AsyncState;
        try
        {
            method.EndInvoke(ar);
        }
        catch (Exception e)
        {
            HandleError(e, method);
        }
    }

    foreach (EventHandler<TEventArgs> handler in eventHandler.GetInvocationList())
        handler.BeginInvoke(sender, args, Callback, handler);
}

因此OnMessageReceived實現可以是:

protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
    => messageReceivedHandler.InvokeAsync(this, e);
  1. 最后, BlockingCollection<T>有一些性能問題,這是一個很大的教訓。 SpinWait內部使用SpinWait ,如果長時間沒有傳入數據,其SpinOnce方法會等待更長時間。 這是一個棘手的問題,因為即使您記錄處理的每一步,您都不會注意到所有內容都已延遲啟動,除非您還可以模擬服務器端。 在這里,您可以使用AutoResetEvent找到快速的BlockingCollection實現,以觸發傳入的數據。 我為它添加了Take(CancellationToken)重載,如下所示:

     /// <summary> /// Takes an item from the <see cref="FastBlockingCollection{T}"/> /// </summary> public T Take(CancellationToken token) { T item; while (!queue.TryDequeue(out item)) { waitHandle.WaitOne(cancellationCheckTimeout); // can be 10-100 ms token.ThrowIfCancellationRequested(); } return item; } 

基本上就是這樣。 也許並非一切都適用於您的情況,例如。 如果幾乎立即的響應並不重要,那么常規的BlockingCollection也會這樣做。

是的,這有點低效,因為你阻止了ThreadPool線程。 我已經討論過這個問題使用Task.Yield來實現生產者/消費者模式時克服ThreadPool飢餓

您還可以在此處查看測試producer -consumer模式的示例: https//github.com/BBGONE/TestThreadAffinity

您可以在循環中使用等待Task.Yield來授予其他任務訪問此線程的權限。

您也可以使用專用線程或更好的自定義ThreadScheduler來解決它,它使用自己的線程池。 但是創建50多個普通線程是無效的。 最好調整任務,這樣會更合作。

如果你使用BlockingCollection(因為它可以在等待寫入( 如果有界 )或者讀取或沒有要讀取的項目時長時間阻塞線程),那么最好使用System.Threading.Tasks.Channels https:// github。 COM / stephentoub / corefxlab /斑點/主/ SRC / System.Threading.Tasks.Channels / README.md

當集合可用於寫入或讀取時,它們不會在等待時阻塞線程。 有一個例子如何使用https://github.com/BBGONE/TestThreadAffinity/tree/master/ThreadingChannelsCoreFX/ChannelsTest

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM