因此,我正在做一个项目,涉及一个液晶屏幕,可以更新60次每秒。它使用BitmapFrame,我需要将这些像素复制到更新屏幕的库中。目前,我得到了大约30-35 FPS,这是太低。所以我尝试使用多线程,但这会带来很多问题。
DisplayController已经创建了一个头来完成如下所有工作:
public void Start()
{
_looper = new Thread(Loop);
_looper.IsBackground = true;
_looper.Start();
}
private void Loop()
{
while (_IsRunning)
{
renderScreen();
}
}它调用renderScreen方法,该方法绘制所有元素并将像素复制到BitmapFrame。但是这个过程太长了,所以我的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#并发队列的文章,但这不是我所需要的。使用两个线程会导致编译器说变量由另一个线程拥有的问题。
我如何同时呈现一个新的位图,并以每秒60次的速度写入LCD?
发布于 2019-10-15 21:34:32
我认为你应该有两个线程(只有两个线程):
以下是我天真的实现。
我使用了一个共享数组,该数组包含最新生成的图像,因为它将分配数保持在较低的水平。共享数组,我们可以使用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();
}
}发布于 2019-10-15 21:03:45
我假设USBD480_DrawFullScreenBGRA32实际上是写到液晶显示器上的,其余的代码只是准备图像。我认为你提高性能的关键是准备下一个图像,而前面的图像正在编写中。
我认为最好的解决方案是使用两个线程,并使用一个ConcurrentQueue作为需要编写的缓冲区。一个线程准备图像和ConcurrentQueue,另一个线程把他们从队列中拉出来将它们写入液晶显示。这样,您就没有每次调用Task.Run的开销。
限制写入队列的帧数也是明智的,这样就不会太超前,占用不必要的内存。
发布于 2019-10-16 01:18:42
您可以考虑使用健壮、高性能和高度可配置的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
}在本例中,使用了两种类型的块,两个TransformBlock和一个ActionBlock。ActionBlock不产生任何输出,因此经常在TPL数据流管道的末尾找到它们。
that的另一种选择是最近引入的一个名为频道的库,它是一个易于学习的小型库。这个选项包括有趣的选项BoundedChannelFullMode,用于选择队列满时删除哪些项:
DropNewest:删除并忽略通道中的最新项,以便为正在写入的项腾出空间。 DropOldest:删除并忽略通道中最古老的项,以便为正在写入的项腾出空间。 DropWrite:删除正在写入的项。 等待:为了完成写入操作,等待空间可用。
相反,只有两个选项。它可以使用演示的block1.Post(...)删除正在编写的项,也可以使用替代的block1.SendAsync(...).Wait()等待空间可用。
通道并不是TPL数据流的完全替代,因为它们只处理工作项的队列,而不处理它们的实际处理。
https://stackoverflow.com/questions/58402150
复制相似问题