借鑑之前的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值。
看來這個東西是內部使用的,不太推薦我們使用。
我們只能使用GetPosition
方法來獲得相對的位置。不過我用的這個裝置,協議是尾部標誌,如果使用PostionOf
的話,偏移量得是負數。在嘗試了很多次不通之後,發現微軟文件中有說到這個潛在問題:
- SequencePosition 是特定 ReadOnlySequence
的位置標記,而不是絕對位置。 由於它是相對於特定 ReadOnlySequence 的,因此如果在其起源的 ReadOnlySequence 之外使用,則沒有意義。 - 不能對沒有 ReadOnlySequence
的 SequencePosition 執行算術運算。 這意味著,執行 position++ 等基本操作將以 ReadOnlySequence .GetPosition(position, 1) 的形式寫入。 - GetPosition(long) 不支援負索引。 這意味著,如果沒有遍歷所有段,就無法獲取倒數第二個字元。
後面還有一些我就不貼了,總之。那我這種情況只能使用buffer.Slice(0, pos)
獲得最長的片段,並將內容傳輸給處理程式進行。
總之,不要使用操作ReadOnlySpan
為了解決操作的複雜性,.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();
}