簡體   English   中英

C#異步液晶寫

[英]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();
    }
}

它調用了繪制所有元素並將像素復制到BitmapFramerenderScreen方法。 但是這個過程需要太長時間,所以我的 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。

我認為你應該有兩個線程(只有兩個):

  1. 一個不斷創建bitmap的;
  2. 一個連續獲取最新的 bitmap 並將其推送到 LCD 的設備。

這是我天真的實現。

我使用了一個包含最新生成的圖像的共享數組,因為它使分配數量保持在較低水平。 一個共享數組,我們可以擺脫 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.

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