C# 避免在处置控件时调用 Invoke

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1874728/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 21:33:15  来源:igfitidea点击:

Avoid calling Invoke when the control is disposed

c#controlsdisposeinvoke

提问by Ozgur Ozcitak

I have the following code in my worker thread (ImageListViewbelow is derived from Control):

我的工作线程中有以下代码(ImageListView以下来自Control):

if (mImageListView != null && 
    mImageListView.IsHandleCreated &&
    !mImageListView.IsDisposed)
{
    if (mImageListView.InvokeRequired)
        mImageListView.Invoke(
            new RefreshDelegateInternal(mImageListView.RefreshInternal));
    else
        mImageListView.RefreshInternal();
}

However, I get an ObjectDisposedExceptionsometimes with the Invokemethod above. It appears that the control can be disposed between the time I check IsDisposedand I call Invoke. How can I avoid that?

但是,我ObjectDisposedException有时会使用上述Invoke方法得到一个。似乎可以在我检查IsDisposed和调用之间处理控件Invoke。我怎样才能避免这种情况?

采纳答案by Hans Passant

There are implicit race conditions in your code. The control can be disposed between your IsDisposed test and the InvokeRequired test. There's another one between InvokeRequired and Invoke(). You can't fix this without ensuring the control outlives the life of the thread. Given that your thread is generating data for a list view, it ought to stop running before the list view disappears.

您的代码中存在隐式竞争条件。该控件可以放置在 IsDisposed 测试和 InvokeRequired 测试之间。InvokeRequired 和 Invoke() 之间还有一个。如果不确保控件的寿命超过线程的生命周期,则无法解决此问题。鉴于您的线程正在为列表视图生成数据,它应该在列表视图消失之前停止运行。

Do so by setting e.Cancel in the FormClosing event and signaling the thread to stop with a ManualResetEvent. When the thread completes, call Form.Close() again. Using BackgroundWorker makes it easy to implement the thread completion logic, find sample code in this post.

通过在 FormClosing 事件中设置 e.Cancel 并用 ManualResetEvent 通知线程停止来实现。当线程完成时,再次调用 Form.Close()。使用 BackgroundWorker 可以轻松实现线程完成逻辑,在这篇文章中找到示例代码。

回答by Bobby

One way might be to call the method itself ones more instead of invoking the ImageListView-Method:

一种方法可能是更多地调用方法本身,而不是调用 ImageListView-Method:

if (mImageListView != null && 
    mImageListView.IsHandleCreated &&
    !mImageListView.IsDisposed)
{
    if (mImageListView.InvokeRequired)
        mImageListView.Invoke(new YourDelegate(thisMethod));
    else
        mImageListView.RefreshInternal();
}

That way it would check one more time before finally calling RefreshInternal().

这样它会在最终调用 RefreshInternal() 之前再检查一次。

回答by oldUser

may be lock(mImageListView){...} ?

可能是 lock(mImageListView){...} ?

回答by Isak Savo

What you have here is a race condition. You're better off just catching the ObjectDisposed exception and be done with it. In fact, I think in this case it is the onlyworking solution.

你在这里拥有的是一个竞争条件。您最好只捕获 ObjectDisposed 异常并完成它。事实上,我认为在这种情况下它是唯一可行的解决方案。

try
{
    if (mImageListView.InvokeRequired)
       mImageListView.Invoke(new YourDelegate(thisMethod));
    else
       mImageListView.RefreshInternal();
} 
catch (ObjectDisposedException ex)
{
    // Do something clever
}

回答by Mongus Pong

You could use mutexes.

你可以使用互斥锁。

Somewhere at the start of the thread :

在线程开始的某个地方:

 Mutex m=new Mutex();

Then :

然后 :

if (mImageListView != null && 
    mImageListView.IsHandleCreated &&
    !mImageListView.IsDisposed)
{
    m.WaitOne(); 

    if (mImageListView.InvokeRequired)
        mImageListView.Invoke(
            new RefreshDelegateInternal(mImageListView.RefreshInternal));
    else
        mImageListView.RefreshInternal();

    m.ReleaseMutex();
}

And whereever it is you are disposing of mImageListView :

无论您在哪里处理 mImageListView :

 m.WaitOne(); 
 mImageListView.Dispose();
 m.ReleaseMutex();

This should ensure you cant dispose and invoke at the same time.

这应该确保您不能同时处理和调用。

回答by csharptest.net

See also this question:

另见这个问题:

Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?

在跨线程 WinForm 事件处理中避免 Invoke/BeginInvoke 的困境?

The utility class that resulted EventHandlerForControlcan solve this problem for event method signatures. You could adapt this class or review the logic therein to solve the issue.

产生EventHandlerForControl的实用程序类可以为事件方法签名解决这个问题。您可以改编本课程或查看其中的逻辑来解决问题。

The real problem here is that nobugz is correct as he points out that the APIs given for cross-thread calls in winforms are inherently not thread safe. Even within the calls to InvokeRequired and Invoke/BeginInvoke themselves there are several race conditions that can cause unexpected behavior.

这里真正的问题是 nobugz 是正确的,因为他指出在 winforms 中为跨线程调用提供的 API 本质上不是线程安全的。即使在对 InvokeRequired 和 Invoke/BeginInvoke 本身的调用中,也存在一些可能导致意外行为的竞争条件。

回答by Ohad Schneider

If a BackGroundWorker is a possibility, there's a very simpleway to circumvent this:

如果有一个 BackGroundWorker 是可能的,那么有一个非常简单的方法来规避这个:

public partial class MyForm : Form
{
    private void InvokeViaBgw(Action action)
    {
        BGW.ReportProgress(0, action);
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (this.IsDisposed) return; //You are on the UI thread now, so no race condition

        var action = (Action)e.UserState;
        action();
    }

    private private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
       //Sample usage:
       this.InvokeViaBgw(() => MyTextBox.Text = "Foo");
    }
}

回答by Oliver

Handle the Form closing event. Check to see if your off UI thread work is still happening, if so start to bring it down, cancel the closing event and then reschedule the close using BeginInvoke on the form control.

处理表单关闭事件。检查您的关闭 UI 线程工作是否仍在进行,如果是,则开始将其关闭,取消关闭事件,然后使用表单控件上的 BeginInvoke 重新安排关闭。

private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
    if (service.IsRunning)
    {
        service.Exit();
        e.Cancel = true;
        this.BeginInvoke(new Action(() => { this.Close(); }));
    }
}

回答by Pierre Poliakoff

The solution proposed by Isak Savo

Isak Savo 提出的解决方案

try
  {
  myForm.Invoke(myForm.myDelegate, new Object[] { message });
  }
catch (ObjectDisposedException)
  { //catch exception if the owner window is already closed
  }

works in C# 4.0 but for some reasons it fails in C#3.0 (the exception is raised anyway)

在 C# 4.0 中工作但由于某些原因它在 C#3.0 中失败(无论如何都会引发异常)

So I used another solution based on a flag indicating if the form is closing and consequently preventing the use of invoke if the flag is set

因此,我使用了另一种基于标志的解决方案,该标志指示表单是否正在关闭并因此在设置标志时阻止使用 invoke

   public partial class Form1 : Form
   {
    bool _closing;
    public bool closing { get { return _closing; } }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        _closing = true;
    }

 ...

 // part executing in another thread: 

 if (_owner.closing == false)
  { // the invoke is skipped if the form is closing
  myForm.Invoke(myForm.myDelegate, new Object[] { message });
  }

This has the advantage of completely avoiding the use of try/catch.

这具有完全避免使用 try/catch 的优点。

回答by goku_da_master

Try using

尝试使用

if(!myControl.Disposing)
    ; // invoke here

I had the exact same problem as you. Ever since I switched to checking .Disposing on the control, the ObjectDisposedException has gone away. Not saying this will fix it 100% of the time, just 99% ;) There is still a chance of a race condition between the check to Disposing and the call to invoke, but in the testing I've done I haven't ran into it (I use the ThreadPool and a worker thread).

我和你有完全一样的问题。自从我切换到在控件上检查 .Disposing 之后,ObjectDisposedException 就消失了。不是说这会在 100% 的情况下修复它,只有 99% ;) 在对 Disposing 的检查和对调用的调用之间仍有可能出现竞争条件,但在我完成的测试中,我没有运行进入它(我使用 ThreadPool 和一个工作线程)。

Here's what I use before each call to invoke:

这是我在每次调用之前使用的内容:

    private bool IsControlValid(Control myControl)
    {
        if (myControl == null) return false;
        if (myControl.IsDisposed) return false;
        if (myControl.Disposing) return false;
        if (!myControl.IsHandleCreated) return false;
        if (AbortThread) return false; // the signal to the thread to stop processing
        return true;
    }