1

我有一种包含 GridView 和一些实用程序按钮的控件。该控件在我的应用程序中随处使用。它通过委托异步填充:

    protected virtual void PopulateGridView()
    {
        if (isPopulating) return;

        //a delegate given to the control by its parent form
        if (GetterMethod != null)
        {
            isPopulating = true;
            /*unimportant UI fluff here*/

            //some controls are fast enough to not have to mess with threading
            if(PopulateSynchronously) 
            {
                PopulateWithGetterMethod();
                InitializeGridView();
            }
            else //most aren't
            {
                Action asyncMethod = PopulateWithGetterMethod;
                asyncMethod.BeginInvoke(
                   ar => Invoke((MethodInvoker)InitializeGridView)), null);
            }
        }
    }

    private void PopulateWithGetterMethod()
    {
        //a list of whetever the control is displaying;
        //the control ancestor and this collection are generic.
        RetrievedInformation = GetterMethod();
    }

    protected virtual void InitializeGridView()
    {
        //use RetrievedInformation to repopulate the GridView;
        //implementation not important, except it touches UI elements,
        //so it needs to be called from the worker thread using Invoke.
    }

在长时间运行的查询中,有时用户会不耐烦并关闭窗口。或者,当其中一个控件基于 Timer 自动刷新时,用户会偶然关闭一个窗口。当这种情况发生并且查询 DID 完成时,回调委托中的 Invoke 调用将失败并出现 InvalidOperationException,因为控件没有窗口句柄。

为了解决这个问题,我尝试使用内置的 IsHandleCreated 属性:

    ...
            else
            {
                Action asyncMethod = PopulateWithGetterMethod;
                asyncMethod.BeginInvoke(
                   ar => { if(IsHandleCreated)
                              Invoke((MethodInvoker)InitializeGridView)); 
                         }, null);
            }

但是,异常仍然会发生,只是不那么频繁。我设法重现它,发现 Invoke 调用仍然发生,即使 IsHandleCreated 上的手表显示为假。我的猜测是,线程在检查和 Invoke 调用之间被抢占了,就像您在提升它之前检查事件委托是否为 null 一样。

我认为我仍然有选择,但我想知道最好的是什么:

  • 不仅要检查 IsHandleCreated,还要检查 Disposing,以确保控件确实存在且完好无损,而不仅仅是即将被销毁。
  • 在进行检查之前执行 Thread.Yield(),以允许操作系统在检查句柄之前进行任何窗口管理。
  • 将 Invoke 调用包装在抑制任何 InvalidOperationExceptions 或至少报告缺少窗口句柄的 try/catch 中。老实说,在这种情况下,我不在乎 GridView 不能更新;用户关闭了窗口,所以显然他们不在乎。让线程安静地死去,而不需要关闭整个应用程序。

第三种选择似乎是一种逃避。必须有一种更清洁的方法来处理它。但是,我不确定其他两个中的任何一个都将是 100% 修复。

编辑:检查 Disposing 和 IsDisposed 也不起作用;我从 if 块中抛出了一个异常,条件为“IsHandleCreated && !Disposing && !IsDisposed”,其中第一个和最后一个节点在观察时为假。目前,我正在使用消息“在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke。”来捕获所有异常,这是我不希望这样做的。

4

2 回答 2

4

是的,有一个 100% 干净的方法可以做到这一点:在允许表单关闭之前终止线程。其他任何事情都是对令人讨厌的削减的创可贴,检查表格是否仍然存在是您无法解决的不可避免的竞争条件。当你调用 Invoke() 时,你只能最小化表单是 gonzo 的可能性,你不能消除它们。

检查这个答案的模式。

于 2011-04-21T16:58:57.123 回答
1

处置是你最好的选择;然而,我们每隔一段时间就会遇到同样的问题,有时 Disposing 调用会返回 false,但是当我们尝试使用它被释放的控件时,即使它是 3 行之后。

在这些情况下,我们发现最好捕获特定异常并验证异常文本是否包含我们正在寻找的内容。如果它是一个异常并且它是一个已知的异常,那么吞下它并不是一个糟糕的主意。问题是确保您只吞下您正在寻找的特定异常。

于 2011-04-21T16:54:45.373 回答