首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C#异步液晶显示写入

C#异步液晶显示写入
EN

Stack Overflow用户
提问于 2019-10-15 20:23:08
回答 3查看 125关注 0票数 2

因此,我正在做一个项目,涉及一个液晶屏幕,可以更新60次每秒。它使用BitmapFrame,我需要将这些像素复制到更新屏幕的库中。目前,我得到了大约30-35 FPS,这是太低。所以我尝试使用多线程,但这会带来很多问题。

DisplayController已经创建了一个头来完成如下所有工作:

代码语言:javascript
复制
public void Start()
{
    _looper = new Thread(Loop);
    _looper.IsBackground = true;
    _looper.Start();
}

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

它调用renderScreen方法,该方法绘制所有元素并将像素复制到BitmapFrame。但是这个过程太长了,所以我的FPS下降了。我试图通过创建一个绘制、复制和写入像素的Task来解决这个问题。但是这个解决方案使用了大量的CPU,并在屏幕上造成了故障。

代码语言:javascript
复制
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#并发队列的文章,但这不是我所需要的。使用两个线程会导致编译器说变量由另一个线程拥有的问题。

我如何同时呈现一个新的位图,并以每秒60次的速度写入LCD?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-10-15 21:34:32

我认为你应该有两个线程(只有两个线程):

  1. 一个不断创建位图的人;以及
  2. 一个连续使用最新位图并将其推送到LCD上的人。

以下是我天真的实现。

我使用了一个共享数组,该数组包含最新生成的图像,因为它将分配数保持在较低的水平。共享数组,我们可以使用3个数组对象(共享+2个线程局部变量)。

代码语言:javascript
复制
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();
    }
}
票数 0
EN

Stack Overflow用户

发布于 2019-10-15 21:03:45

我假设USBD480_DrawFullScreenBGRA32实际上是写到液晶显示器上的,其余的代码只是准备图像。我认为你提高性能的关键是准备下一个图像,而前面的图像正在编写中。

我认为最好的解决方案是使用两个线程,并使用一个ConcurrentQueue作为需要编写的缓冲区。一个线程准备图像和ConcurrentQueue,另一个线程把他们从队列中拉出来将它们写入液晶显示。这样,您就没有每次调用Task.Run的开销。

限制写入队列的帧数也是明智的,这样就不会太超前,占用不必要的内存。

票数 3
EN

Stack Overflow用户

发布于 2019-10-16 01:18:42

您可以考虑使用健壮、高性能和高度可配置的TPL数据流库,这将允许您构建一个数据管道。您将将原始数据投递到管道的第一个块中,数据将在从一个块流到下一个块时被转换,最后在最后一个块中呈现。所有区块都将并行工作。在下面的示例中,有三个块,它们都配置了默认的MaxDegreeOfParallelism = 1,因此最多3个线程将同时忙于工作。我已经用一个有意设置的小BoundedCapacity来配置块,这样如果传入的原始数据超过管道所能处理的内容,过多的输入就会被删除。

代码语言:javascript
复制
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
});

管道是通过将块连接在一起创建的:

代码语言:javascript
复制
block1.LinkTo(block2, new DataflowLinkOptions() { PropagateCompletion = true });
block2.LinkTo(block3, new DataflowLinkOptions() { PropagateCompletion = true });

最后,循环的形式如下:

代码语言:javascript
复制
void Loop()
{
    while (_IsRunning)
    {
        block1.Post(GetRawStreamData());
    }
    block1.Complete();
    block3.Completion.Wait(); // Optional, to wait for the last data to be processed
}

在本例中,使用了两种类型的块,两个TransformBlock和一个ActionBlockActionBlock不产生任何输出,因此经常在TPL数据流管道的末尾找到它们。

that的另一种选择是最近引入的一个名为频道的库,它是一个易于学习的小型库。这个选项包括有趣的选项BoundedChannelFullMode,用于选择队列满时删除哪些项:

DropNewest:删除并忽略通道中的最新项,以便为正在写入的项腾出空间。 DropOldest:删除并忽略通道中最古老的项,以便为正在写入的项腾出空间。 DropWrite:删除正在写入的项。 等待:为了完成写入操作,等待空间可用。

相反,只有两个选项。它可以使用演示的block1.Post(...)删除正在编写的项,也可以使用替代的block1.SendAsync(...).Wait()等待空间可用。

通道并不是TPL数据流的完全替代,因为它们只处理工作项的队列,而不处理它们的实际处理。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58402150

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档