8

似乎在 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 个句柄/秒。

4

4 回答 4

6

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

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

如果不需要Invoke 调用是同步的,使用 BeginInvoke 代替 Invoke 并且不要调用 EndInvoke;最终结果将做同样的事情,但不会创建或“泄漏”句柄。

于 2009-10-21T20:40:15.203 回答
2

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

于 2009-10-21T19:48:56.290 回答
2

好的,我给了它一点时间,看起来它实际上并没有泄漏句柄,这只是垃圾收集器的不确定性。我将它提高到每滴答声 10 毫秒,它会非常快速地爬升,30 秒后又会回落。

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

于 2009-10-21T20:44:55.403 回答
0

有趣 - 这不是一个答案,但根据 Andrei 的评论,我原以为这不会以相同的方式泄漏句柄,但是它确实以 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);
    }
于 2009-10-21T20:00:26.693 回答