[英]Improving Bitmap retrieval (from screen/window) speed and storage for multi-threaded access using C#
我在尝试即时读取屏幕截图以及对这些屏幕截图(位图)的多线程访问时遇到了一些问题。
首先,捕获屏幕数据我有以下代码:
NativeMethods.GetWindowRect(HandleDisplay, out r);
Size s = new Size(r.Right - r.Left, r.Bottom - r.Top);
Bitmap b = new Bitmap(s.Width, s.Height, PixelFormat.Format24bppRgb);
using (Graphics gfxBmp = Graphics.FromImage(b))
{
try {
gfxBmp.CopyFromScreen(r.Left, r.Top, 0, 0, s, CopyPixelOperation.SourceCopy);
}
finally
{
gfxBmp.Dispose();
}
}
现在,代码可以按预期工作,并且创建的位图正确。 我在这段代码中遇到的第一个问题是速度。 我没有以毫秒为单位超时执行速度,但是我可以说代码被包装并执行以在其自己的线程中重复更新位图,并且在1000 x 600像素的屏幕上,它以45左右的速度更新。每秒50次。 如果屏幕尺寸较大,则执行速度会大大下降,例如,捕获1440 x 900的窗口会将更新速度降低到每秒20次或更少。
我遇到的第二个问题是位图是从一个线程重复存储/更新的,而我还有其他线程需要访问该位图(或其中的一部分)。 问题是,当主更新线程写入位图,而另一个线程尝试读取位图时,显然出现了“对象已在其他地方使用”错误。 多线程是指这样的任务:
Task t = Task.Run(() => {
while(true) {
try {
token.ThrowIfCancellationRequested();
// update/process the data
...
} catch {
break;
}
}, token);
那么,最好的存储位图的方法(例如,以位图或byte []或其他方式)并从其他线程访问它。 我一直在考虑将其转换为byte []数据的想法(使用LockBits和Marshal.Copy),但尚未确定这是否是正确的路由。
问题:
有没有一种方法可以提高获取图像数据的速度(也许使用byte []并以某种方式仅“更新”更改后的像素数据)。
如何使它线程安全。
注意:窗口可能不是DirectX / OpenGL(这就是为什么我最初选择此方法而不是其他方法的原因)
编辑/更新:
我进行了以下更改,从而略微提高了性能。 -gfxBmp现在是使用实例构造函数初始化的静态属性-删除了事件处理程序(类不再需要引发通知图像已更新的事件)-对图像属性的访问由ReaderWriterLockSlim封装(从其他线程读取似乎是工作出色)
如前所述,所有可能的开销都已删除,任务现在看起来像这样:
cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
IsRunning = true;
Task t = Task.Run(() =>
{
while (IsRunning)
{
try
{
token.ThrowIfCancellationRequested();
Refresh();
CountFrame();
}
catch
{
IsRunning = false;
break;
}
}
}, token);
我的刷新方法如下:
private void Refresh()
{
NativeMethods.Rect r = new NativeMethods.Rect();
NativeMethods.GetWindowRect(hWndDisplay, out r);
if (r != rect) Rect = r;
gfx.CopyFromScreen(rect.Left, rect.Top, 0, 0, size, CopyPixelOperation.SourceCopy);
}
CountFrame与我在有无以下基准测试时的速度无关:
With CountFrame (Screen size captured 872px x 480px ~60fps):
Iterations: 1000, Total: 16.20 s, Average: 16.195 ms
Iterations: 1000, Total: 16.18 s, Average: 16.178 ms
Iterations: 1000, Total: 16.44 s, Average: 16.44 ms
Without CountFrame (Screen size captured 872px x 480px ~60fps):
Iterations: 1000, Total: 16.21 s, Average: 16.213 ms
Iterations: 1000, Total: 16.17 s, Average: 16.17 ms
Iterations: 1000, Total: 16.18 s, Average: 16.183 ms
Without CountFrame (Screen size captured 1402px x 828px ~32fps)
Iterations: 1000, Total: 29.74 s, Average: 29.744 ms
Iterations: 1000, Total: 30.08 s, Average: 30.083 ms
Iterations: 1000, Total: 29.44 s, Average: 29.444 ms
因此,线程安全性问题似乎已解决,已按照说明进行了优化。 因此,我现在仍然在速度上挣扎。 如您所见,FrameRate降为1402 x 828,更好的捕获方式仍然不清楚。
目前,我正在使用Graphics.CopyFromScreen(...)来获取像素,据我了解内部使用bitblt。 是否可能存在另一种从屏幕区域获取数据的方法,例如通过像素/字节数据比较并仅更新更改的信息或我应该查看的其他资源?
非常感谢@DiskJunky,因为他的评论才是真正的答案:
1.线程安全 -将Graphics.CopyFromScreen()逻辑移到每个准备就绪的位置,并且仅读取屏幕的必要部分以供读者使用。 这允许阅读器“拥有”单个位图,从而无需线程锁定。
2.性能 -有了上述功能,阅读器现在由于包含CopyFromScreen而变得有点重,但是消除了裁剪所需的共享屏幕部分和锁定逻辑的需要(代码)。 同样,在可能的地方重构了代码,以便在可能的情况下实例化和重用阅读器类中的属性(即,不再需要在每次调用CopyFromScreen时都旋转一个新的Graphic对象),从而减少了开销和时钟周期。
这归结为最终重新思考了我处理逻辑的方式:
最初的逻辑是要有一个线程来不断更新位图,并在需要时将其共享给所有需要它的读者。
优化的逻辑使读者可以只获取必要的信息,必要时直接从源头获取信息,而不是中间人。 以更少的开销生产更干净的代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.