简体   繁体   English

C#异步液晶写

[英]C# asynchronous LCD write

So I'm working on a project that involves a LCD screen that can update 60 times per second.所以我正在做一个涉及每秒可以更新 60 次的 LCD 屏幕的项目。 It uses a BitmapFrame and I need to copy those pixels to a library that updates the screen.它使用BitmapFrame ,我需要将这些像素复制到更新屏幕的库中。 Currently I'm getting about 30-35 FPS which is too low.目前我得到大约 30-35 FPS,这太低了。 So I'm trying to use multi-threading but this creates a lot of problems.所以我正在尝试使用多线程,但这会产生很多问题。

The DisplayController already creates a thead to do all the work on like so: DisplayController 已经创建了一个thead 来完成所有工作,如下所示:

public void Start()
{
    _looper = new Thread(Loop);
    _looper.IsBackground = true;
    _looper.Start();
}

private void Loop()
{
    while (_IsRunning)
    {
        renderScreen();
    }
}

Which calls the renderScreen method that draws all the elements and copies the pixels to the BitmapFrame .它调用了绘制所有元素并将像素复制到BitmapFramerenderScreen方法。 But this proces takes too long so my FPS drops.但是这个过程需要太长时间,所以我的 FPS 下降了。 My attempt to solve this problem was by creating a Task that draws, copies and writes the pixels.我尝试通过创建一个绘制、复制和写入像素的Task来解决这个问题。 But this solution uses a lot of CPU and causes glitchtes on the screen.但是这种解决方案会占用大量 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);
    });
}

I've been reading a lot about concurrent queues for C# but that's not what I need.我一直在阅读很多关于 C# 的并发队列的信息,但这不是我需要的。 And using two threads causes the issue that the compiler says that the variable is owned by another thread.并且使用两个线程会导致编译器说该变量由另一个线程拥有的问题。

How can I concurrently render a new bitmap and write that bitmap 60 times per second to the LCD?如何同时渲染新的 bitmap 并将 bitmap 每秒写入 LCD 60 次?

I assume that USBD480_DrawFullScreenBGRA32 is what actually writes to the LCD, and the rest of the code just prepares the image.我假设USBD480_DrawFullScreenBGRA32是实际写入 LCD 的内容,而代码的 rest 只是准备图像。 I think your key to better performance is preparing the next image while the previous image is being written.我认为您提高性能的关键是在编写前一个图像准备下一个图像。

I think your best solution is to use two threads and use a ConcurrentQueue as a buffer for what needs to be written.我认为您最好的解决方案是使用两个线程并使用ConcurrentQueue作为需要写入的缓冲区。 One thread prepares the images and puts them into the ConcurrentQueue , and the other thread pulls them off the queue and writes them to the LCD.一个线程准备图像并将它们放入ConcurrentQueue ,另一个线程将它们从队列中拉出并将它们写入 LCD。 This way you don't have the overhead of calling Task.Run each time around.这样您就没有每次调用Task.Run的开销。

It might also be wise to limit how many frames are written to the queue, so it doesn't get too far ahead and take up unnecessary memory.限制写入队列的帧数可能也是明智之举,这样它就不会超前并占用不必要的 memory。

I think you should have two threads (and two only):我认为你应该有两个线程(只有两个):

  1. one that continuously creates the bitmap;一个不断创建bitmap的; and
  2. one that continuously takes the most recent bitmap and pushes it to LCD.一个连续获取最新的 bitmap 并将其推送到 LCD 的设备。

Here's my naive implementation.这是我天真的实现。

I used a shared array that contains the latest produced image because it keeps number of allocations low.我使用了一个包含最新生成的图像的共享数组,因为它使分配数量保持在较低水平。 A shared array we can get away with 3 array objects (shared + 2 thread locals).一个共享数组,我们可以摆脱 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();
    }
}

You could consider using the robust, performant, and highly configurable TPL Dataflow library , that will allow you to construct a pipeline of data.您可以考虑使用健壮、高性能且高度可配置的TPL 数据流库,这将允许您构建数据管道。 You will be posting raw data into the first block of the pipeline, and the data will be transformed while flowing from one block to the next, before being finally rendered at the last block.您会将原始数据发布到管道的第一个块中,并且数据将在从一个块流向下一个块时进行转换,然后最终在最后一个块处呈现。 All blocks will be working in parallel.所有块将并行工作。 In the example bellow there are three blocks, all configured with the default MaxDegreeOfParallelism = 1 , so 3 threads at maximum will be concurrently busy doing work.在下面的示例中,有三个块,都配置了默认的MaxDegreeOfParallelism = 1 ,因此最多 3 个线程将同时忙于工作。 I have configured the blocks with an intentionally small BoundedCapacity , so that if the incoming raw data is more than what the pipeline can process, the excessive input will be dropped.我故意为这些块配置了一个小的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
});

The pipeline is created by linking the blocks together:通过将块链接在一起来创建管道:

block1.LinkTo(block2, new DataflowLinkOptions() { PropagateCompletion = true });
block2.LinkTo(block3, new DataflowLinkOptions() { PropagateCompletion = true });

And finally the loop takes the form bellow:最后循环采用以下形式:

void Loop()
{
    while (_IsRunning)
    {
        block1.Post(GetRawStreamData());
    }
    block1.Complete();
    block3.Completion.Wait(); // Optional, to wait for the last data to be processed
}

In this example 2 types of blocks are used, two TransformBlock s and one ActionBlock at the end.在这个例子中,使用了 2 种类型的块,两种TransformBlock和最后的一种ActionBlock The ActionBlock s do not produce any output, so they are frequently found at the end of TPL Dataflow pipelines. ActionBlock不会产生任何 output,因此它们经常出现在 TPL 数据流管道的末端。

An alternative to TPL Dataflow is a recently introduced library named Channels , a small library that is easy to learn. TPL Dataflow 的替代方案是最近推出的名为Channels的库,这是一个易于学习的小型库。 This one includes the interesting option BoundedChannelFullMode , for selecting what items are dropped when the queue is full:这包括有趣的选项BoundedChannelFullMode ,用于选择队列已满时丢弃哪些项目:

DropNewest: Removes and ignores the newest item in the channel in order to make room for the item being written. DropNewest:删除并忽略频道中的最新项目,以便为正在写入的项目腾出空间。
DropOldest: Removes and ignores the oldest item in the channel in order to make room for the item being written. DropOldest:删除并忽略频道中最旧的项目,以便为正在写入的项目腾出空间。
DropWrite: Drops the item being written. DropWrite:删除正在写入的项目。
Wait: Waits for space to be available in order to complete the write operation.等待:等待空间可用以完成写入操作。

In contrast TPL Dataflow has only two options.相比之下,TPL Dataflow 只有两个选项。 It can ether drop the item being written by using the demonstrated block1.Post(...) , or wait for space to be available by using the alternative block1.SendAsync(...).Wait() .它可以通过使用演示的block1.Post(...)来删除正在写入的项目,或者通过使用替代的block1.SendAsync(...).Wait()等待空间可用。

Channels are not a complete replacement of TPL Dataflow though, since they deal only with the queuing of the workitems, and not with their actual processing.但是,通道并不是 TPL 数据流的完全替代品,因为它们只处理工作项的排队,而不是它们的实际处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM