串列埠使用Pipeline時詭異的ReadOnlySequence問題

波多爾斯基發表於2021-01-18

借鑑之前的Pipeline的操作方式,現在目標是給串列埠讀取操作也使用上Pipeline。稍微改造一下,以下程式碼可以直接執行。

協議為使用連續的4個0XFF作為結尾,沒有頭標誌。資料總長為68位定長。

我需要判斷從開始到選定位置是否長度足夠,這裡面用來判斷segment長度我用了這個方式。

buffer.Slice(0, start.Value).Length >= 64

其實最早不是使用這個東西的,而是使用的SequencePosition的GetInteger()方法,獲取到了位置的index,自然就知道了長度等資訊,而且非常方便進行擷取操作。可是在使用的時候,發現一個非常詭異的問題:通過這個方法獲取到的index值要大於Buffer的總長度。Slice直接彈出ArgumentOutOfRangeException,但是不彈出錯誤,除錯的時候非常麻煩。

檢視這個方法定義的時候,發現簽名是這樣的:

[EditorBrowsable(EditorBrowsableState.Never)]
public int GetInteger();

這個東西第一次見到,VS並不會提示,但是你強行寫的話,能夠正常編譯。看來微軟並不是很像讓我們看到這個玩意。仔細挖掘一下,發現通過PositionOf方法獲得的SequencePosition內部引用了一段長度為4096的記憶體。這個GetInteger()有時候返回的是在這段Memory上面的Index值。

看來這個東西是內部使用的,不太推薦我們使用。

有幾個問答也說到了這個事情:問答1問答2

我們只能使用GetPosition方法來獲得相對的位置。不過我用的這個裝置,協議是尾部標誌,如果使用PostionOf的話,偏移量得是負數。在嘗試了很多次不通之後,發現微軟文件中有說到這個潛在問題:

  • SequencePosition 是特定 ReadOnlySequence 的位置標記,而不是絕對位置。 由於它是相對於特定 ReadOnlySequence 的,因此如果在其起源的 ReadOnlySequence 之外使用,則沒有意義。
  • 不能對沒有 ReadOnlySequence 的 SequencePosition 執行算術運算。 這意味著,執行 position++ 等基本操作將以 ReadOnlySequence.GetPosition(position, 1) 的形式寫入。
  • GetPosition(long) 不支援負索引。 這意味著,如果沒有遍歷所有段,就無法獲取倒數第二個字元。
    後面還有一些我就不貼了,總之。那我這種情況只能使用buffer.Slice(0, pos)獲得最長的片段,並將內容傳輸給處理程式進行。

總之,不要使用操作ReadOnlySpan/ReadOnlyMemory的思路來操作ReadOnlySequence/SequencePosition!

為了解決操作的複雜性,.NET Core 3.0引入了一個SequenceReader<T>簡化了操作,以後有機會使用的時候在寫吧。

最後程式如下:


private async void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort p = sender as SerialPort;
            byte[] bytes = new byte[1024 * 4];
            var dataCount = p.Read(bytes, 0, p.BytesToRead);
            var span = bytes.AsMemory().Slice(0, dataCount);
            await FillPipeAsync(span);
        }

private void InitPipe()
        {
            Pipe pipe = new Pipe();
            writer = pipe.Writer;
            //Task writing = FillPipeAsync(port, pipe.Writer);
            Task.Run(() => ReadPipeAsync(pipe.Reader));
            //await Task.WhenAll(reading, writing);
        }

        private PipeWriter writer;

        private async Task FillPipeAsync(ReadOnlyMemory<byte> memory)
        {
            await writer.WriteAsync(memory);
            //writer.Advance(memory.Length);

            // Make the data available to the PipeReader
            FlushResult result = await writer.FlushAsync();
            if (result.IsCompleted)
                writer.Complete();
        }

        private async Task ReadPipeAsync(PipeReader reader)
        {
            while (true)
            {
                try
                {
                    ReadResult result = await reader.ReadAsync();
                    ReadOnlySequence<byte> buffer = result.Buffer;

                    SequencePosition? start = null;
                    var headBytes = new byte[] { 0xff, 0xff, 0xff, 0xff };

                    do
                    {


                        // Find the EOL
                        start = buffer.PositionOf(headBytes[0]);

                        if (start != null)
                        {
                            if (buffer.Slice(start.Value).Length >= 4)
                            {
                                var headtoCheck = buffer.Slice(start.Value, 4).ToArray();
                                if (headtoCheck.SequenceEqual(headBytes))
                                {
                                    if(buffer.Slice(0, start.Value).Length >= 64)
                                    {
                                        var pos = buffer.GetPosition(4, start.Value);
                                        var mes = buffer.Slice(0, pos);
                                        DataProcess.Process(mes.ToArray());
                                        var next = buffer.GetPosition(4, start.Value);
                                        buffer = buffer.Slice(next);
                                    }
                                    else
                                    {
                                        var next = buffer.GetPosition(4, start.Value);
                                        buffer = buffer.Slice(next);
                                    }
                                }
                                else
                                {
                                    var next = buffer.GetPosition(1, start.Value);
                                    buffer = buffer.Slice(next);
                                }
                            }
                            else
                            {
                                var next = buffer.GetPosition(1, start.Value);
                                buffer = buffer.Slice(next);
                            }
                        }


                    }
                    while (start != null);
                    // We sliced the buffer until no more data could be processed
                    // Tell the PipeReader how much we consumed and how much we left to process

                    if (result.IsCompleted)
                    {
                        continue;
                    }
                    reader.AdvanceTo(buffer.Start, buffer.End);
                }
                catch (ArgumentOutOfRangeException e)
                {

                    //throw e;
                }
            }
            reader.Complete();
        }

相關文章