我知道之前有人问过这个问题,但我的问题直接与在图片框上使用“拉伸(stretch)”(或“正常”以外的任何其他内容)有关,“正常”效果很好。
我有一个简单的应用程序,基本上每秒截屏 60 次 _timer.Interval = (1.0/60.0) * 1000.0;
使用计时器。不幸的是,使用“拉伸(stretch)”会导致使用越来越多的内存,最终导致应用程序崩溃。
public partial class Form1 : Form
{
System.Timers.Timer _timer = new System.Timers.Timer();
public Form1()
{
InitializeComponent();
_timer.Elapsed += new ElapsedEventHandler(OnElapsed);
_timer.Interval = (1.0/60.0) * 1000.0;
}
private void btnCapture_Click(object sender, EventArgs e)
{
_timer.Enabled = true;
}
private void OnElapsed(object o, ElapsedEventArgs e)
{
Rectangle bounds = Screen.PrimaryScreen.Bounds;
Bitmap bmp = new Bitmap(bounds.Width, bounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width, bounds.Height));
using (Image prev = pbCapture.Image)
{
pbCapture.Image = bmp;
}
}
}
}
如您所见,我处理了图形 IDisposable 和图片框使用的先前图像。但是,内存仍然增加。
我做错了什么吗?
最佳答案
以如此高的速率创建大量大位图通常是一种糟糕的方法。我建议您创建一次缓存位图以减少内存压力。您也可以使用任何 GDI 函数直接复制位图数据。
您的内存泄漏原因是多线程问题。 Windows 上的默认计时器分辨率为 15.6 毫秒。在 60 赫兹(16.67 毫秒)的速率下,很可能至少有两个事件几乎同时被触发。
很明显,在这种情况下,您会遇到竞争条件,因此您的位图将无法正确处理,并且会卡在内存中(在终结器队列中)。由于您的 CPU 负载过重,垃圾收集器无法有效地清除僵尸对象。
要检查这一点,只需在事件处理程序中设置一个简单的 lock
部分,这样一次只有一个线程可以访问位图。您会注意到不会再发生内存泄漏。
您当然可以将计时器分辨率更改为例如1 毫秒。但是你必须保证你的位图处理会在下一个事件到来之前完成。这在 .NET 世界中很难做到。
另一个解决方案是将 System.Timers.Timer
更改为 System.Windows.Forms.Timer
。后者将在 GUI 线程而不是线程池线程上引发事件。但您必须记住,所有处理都将在 GUI 线程上完成,这会使您的用户界面无响应。
也许最好的解决方案是实现一些“掉帧”行为。如果一帧到达时前一帧仍在处理中,则只需丢弃新帧。你可以用例如一个信号量
。
另一种解决方案,如@BradleyUffner 所建议的那样,是使计时器不可重复,并在处理完一帧后启用下一个事件处理程序。
关于C# Picturebox 拉伸(stretch)内存问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46848232/