从System.Threading.Timer在UI中调用时如何避免泄漏句柄?

看起来在System.Threading.Timer回调函数的回调中调用Winforms控件上的Invoke,直到计时器被释放。 有没有人知道如何解决这个问题? 我需要每秒轮询一次值并相应地更新UI。

我在一个测试项目中尝试过,以确保这确实是泄漏的原因,这只是以下几点:

System.Threading.Timer timer; public Form1() { InitializeComponent(); timer = new System.Threading.Timer(new System.Threading.TimerCallback(DoStuff), null, 0, 500); } void DoStuff(object o) { this.Invoke(new Action(() => this.Text = "hello world")); } 

如果您在Windows任务管理器中观看,这将泄漏2个句柄/秒。

Invoke的行为类似于BeginInvoke / EndInvoke对,因为它将消息发布到UI线程,创建句柄,并等待该句柄以确定Invoked方法何时完成。 这个句柄正在“泄漏”。 您可以看到这些是未命名的事件,使用Process Explorer在应用程序运行时监视句柄。

如果IASyncResult是IDisposable,则处理对象将负责清理句柄。 事实并非如此,当垃圾收集器运行并调用IASyncResult对象的终结器时,句柄会被清除。 你可以通过在每20次调用DoStuff后添加一个GC.Collect()来看到这一点 – 句柄计数每20秒就会丢失一次。 当然,通过添加对GC.Collect()的调用来“解决”问题是解决问题的错误方法。 让垃圾收集器自己完成工作。

如果您不需要将Invoke调用设置为同步,请使用BeginInvoke而不是Invoke,并且不要调用EndInvoke; 最终结果将做同样的事情,但不会创建或“泄露”句柄。

有什么理由你不能在这里使用System.Windows.Forms.Timer? 如果计时器绑定到该表单,您甚至不需要调用。

好吧,我给了它一点时间,看起来它实际上没有泄漏句柄,它只是垃圾收集器的不确定性。 我每次嘀嗒一声撞到它的速度达到10毫秒,它的爬升速度非常快,30秒后会下降。

为了确认我在每次回调时手动调用GC.Collect()的理论(不要在实际项目中这样做,这只是为了测试,那里有很多关于为什么这是个坏主意的文章)并且句柄计数是稳定的。

有趣 – 这不是一个答案,但根据安德烈的评论,我会认为这不会泄漏处理相同的方式,但它确实以OP提到的相同速率泄漏句柄。

 System.Threading.Timer timer; public Form2() { InitializeComponent(); } private void UpdateFormTextCallback() { this.Text = "Hello World!"; } private Action UpdateFormText; private void DoStuff(object value) { this.Invoke(UpdateFormText); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); timer = new System.Threading.Timer(new TimerCallback(DoStuff), null, 0, 500); UpdateFormText = new Action(UpdateFormTextCallback); }