繁体   English   中英

使用C#改进位图检索(从屏幕/窗口)的速度和存储,以进行多线程访问

[英]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),但尚未确定这是否是正确的路由。

问题:

  1. 有没有一种方法可以提高获取图像数据的速度(也许使用byte []并以某种方式仅“更新”更改后的像素数据)。

  2. 如何使它线程安全。

    • 我将有一个主线程不断对其进行更新(仅写)
    • 我将有很多线程需要全部或部分读取(只读)

注意:窗口可能不是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.

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