我在尝试即时读取屏幕截图以及对这些屏幕截图(位图)进行多线程访问时遇到了一些问题。
对于初学者来说,捕获屏幕数据我有以下代码:
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 对象),从而减少开销和时钟周期。
归结为最终重新思考我处理逻辑的方式:
原始逻辑是让一个线程不断更新位图,并在需要时将其共享给所有需要它的读者。
优化的逻辑让读者处理只获取必要信息的问题,并且在必要时直接从源头获取信息,而不是通过中间人。以更少的开销生成更清晰的代码。
关于c# - 使用 C# 提高多线程访问的位图检索(从屏幕/窗口)速度和存储,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46430977/