[英]C# asynchronous LCD write
所以我正在做一個涉及每秒可以更新 60 次的 LCD 屏幕的項目。 它使用BitmapFrame
,我需要將這些像素復制到更新屏幕的庫中。 目前我得到大約 30-35 FPS,這太低了。 所以我正在嘗試使用多線程,但這會產生很多問題。
DisplayController 已經創建了一個thead 來完成所有工作,如下所示:
public void Start()
{
_looper = new Thread(Loop);
_looper.IsBackground = true;
_looper.Start();
}
private void Loop()
{
while (_IsRunning)
{
renderScreen();
}
}
它調用了繪制所有元素並將像素復制到BitmapFrame
的renderScreen
方法。 但是這個過程需要太長時間,所以我的 FPS 下降了。 我嘗試通過創建一個繪制、復制和寫入像素的Task
來解決這個問題。 但是這種解決方案會占用大量 CPU 並導致屏幕出現故障。
public void renderScreen()
{
Task.Run(() =>
{
Monitor.Enter(_object);
// Push screen to LCD
BitmapFrame bf = BitmapFrame.Create(screen);
RenderOptions.SetBitmapScalingMode(bf, BitmapScalingMode.LowQuality);
bf.CopyPixels(new Int32Rect(0, 0, width, height), pixels, width * 4, 0);
DisplayWrapper.USBD480_DrawFullScreenBGRA32(ref disp, pixels);
Monitor.Exit(_object);
});
}
我一直在閱讀很多關於 C# 的並發隊列的信息,但這不是我需要的。 並且使用兩個線程會導致編譯器說該變量由另一個線程擁有的問題。
如何同時渲染新的 bitmap 並將 bitmap 每秒寫入 LCD 60 次?
我假設USBD480_DrawFullScreenBGRA32
是實際寫入 LCD 的內容,而代碼的 rest 只是准備圖像。 我認為您提高性能的關鍵是在編寫前一個圖像時准備下一個圖像。
我認為您最好的解決方案是使用兩個線程並使用ConcurrentQueue
作為需要寫入的緩沖區。 一個線程准備圖像並將它們放入ConcurrentQueue
,另一個線程將它們從隊列中拉出並將它們寫入 LCD。 這樣您就沒有每次調用Task.Run
的開銷。
限制寫入隊列的幀數可能也是明智之舉,這樣它就不會超前並占用不必要的 memory。
我認為你應該有兩個線程(只有兩個):
這是我天真的實現。
我使用了一個包含最新生成的圖像的共享數組,因為它使分配數量保持在較低水平。 一個共享數組,我們可以擺脫 3 個數組對象(共享 + 2 個線程本地)。
public class Program
{
public class A
{
private readonly object pixelsLock = new object();
Array shared = ...;
public void Method2()
{
Array myPixels = (...);
while (true)
{
// Prepare image
BitmapFrame bf = BitmapFrame.Create(screen);
RenderOptions.SetBitmapScalingMode(bf, BitmapScalingMode.LowQuality);
bf.CopyPixels(new Int32Rect(0, 0, width, height), myPixels, width * 4, 0);
lock (pixelsLock)
{
// Copy the hard work to shared storage
Array.Copy(sourceArray: myPixels, destinationArray: shared, length: myPixels.GetUpperBound(0) - 1);
}
}
}
public void Method1()
{
Array myPixels = (...);
while (true)
{
lock (pixelsLock)
{
//Max a local copy
Array.Copy(sourceArray: shared, destinationArray: myPixels, length: myPixels.GetUpperBound(0) - 1);
}
DisplayWrapper.USBD480_DrawFullScreenBGRA32(ref disp, myPixels);
}
}
}
public static async Task Main(string[] args)
{
var a = new A();
new Thread(new ThreadStart(a.Method1)).Start();
new Thread(new ThreadStart(a.Method2)).Start();
Console.ReadLine();
}
}
您可以考慮使用健壯、高性能且高度可配置的TPL 數據流庫,這將允許您構建數據管道。 您會將原始數據發布到管道的第一個塊中,並且數據將在從一個塊流向下一個塊時進行轉換,然后最終在最后一個塊處呈現。 所有塊將並行工作。 在下面的示例中,有三個塊,都配置了默認的MaxDegreeOfParallelism = 1
,因此最多 3 個線程將同時忙於工作。 我故意為這些塊配置了一個小的BoundedCapacity
,這樣如果傳入的原始數據超過管道可以處理的數據,就會丟棄過多的輸入。
var block1 = new TransformBlock<Stream, BitmapFrame>(stream =>
{
BitmapFrame bf = BitmapFrame.Create(stream);
RenderOptions.SetBitmapScalingMode(bf, BitmapScalingMode.LowQuality);
return bf;
}, new ExecutionDataflowBlockOptions()
{
BoundedCapacity = 5
});
var block2 = new TransformBlock<BitmapFrame, int[]>(bf =>
{
var pixels = new int[width * height * 4];
bf.CopyPixels(new Int32Rect(0, 0, width, height), pixels, width * 4, 0);
return pixels;
}, new ExecutionDataflowBlockOptions()
{
BoundedCapacity = 5
});
var block3 = new ActionBlock<int[]>(pixels =>
{
DisplayWrapper.USBD480_DrawFullScreenBGRA32(ref disp, pixels);
}, new ExecutionDataflowBlockOptions()
{
BoundedCapacity = 5
});
通過將塊鏈接在一起來創建管道:
block1.LinkTo(block2, new DataflowLinkOptions() { PropagateCompletion = true });
block2.LinkTo(block3, new DataflowLinkOptions() { PropagateCompletion = true });
最后循環采用以下形式:
void Loop()
{
while (_IsRunning)
{
block1.Post(GetRawStreamData());
}
block1.Complete();
block3.Completion.Wait(); // Optional, to wait for the last data to be processed
}
在這個例子中,使用了 2 種類型的塊,兩種TransformBlock
和最后的一種ActionBlock
。 ActionBlock
不會產生任何 output,因此它們經常出現在 TPL 數據流管道的末端。
TPL Dataflow 的替代方案是最近推出的名為Channels的庫,這是一個易於學習的小型庫。 這包括有趣的選項BoundedChannelFullMode
,用於選擇隊列已滿時丟棄哪些項目:
DropNewest:刪除並忽略頻道中的最新項目,以便為正在寫入的項目騰出空間。
DropOldest:刪除並忽略頻道中最舊的項目,以便為正在寫入的項目騰出空間。
DropWrite:刪除正在寫入的項目。
等待:等待空間可用以完成寫入操作。
相比之下,TPL Dataflow 只有兩個選項。 它可以通過使用演示的block1.Post(...)
來刪除正在寫入的項目,或者通過使用替代的block1.SendAsync(...).Wait()
等待空間可用。
但是,通道並不是 TPL 數據流的完全替代品,因為它們只處理工作項的排隊,而不是它們的實際處理。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.