[英]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.