Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/blackberry/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 避免在释放控件时调用Invoke_C#_Controls_Dispose_Invoke - Fatal编程技术网

C# 避免在释放控件时调用Invoke

C# 避免在释放控件时调用Invoke,c#,controls,dispose,invoke,C#,Controls,Dispose,Invoke,我的工作线程中有以下代码(ImageListView下面是从Control派生的): 但是,我有时会使用上面的Invoke方法得到一个ObjectDisposedException。似乎可以在我检查IsDisposed和调用Invoke之间释放控件。如何避免这种情况?一种方法可能是调用方法本身,而不是调用ImageListView方法: if (mImageListView != null && mImageListView.IsHandleCreated &&

我的工作线程中有以下代码(
ImageListView
下面是从
Control
派生的):


但是,我有时会使用上面的
Invoke
方法得到一个
ObjectDisposedException
。似乎可以在我检查
IsDisposed
和调用
Invoke
之间释放控件。如何避免这种情况?

一种方法可能是调用方法本身,而不是调用ImageListView方法:

if (mImageListView != null && 
    mImageListView.IsHandleCreated &&
    !mImageListView.IsDisposed)
{
    if (mImageListView.InvokeRequired)
        mImageListView.Invoke(new YourDelegate(thisMethod));
    else
        mImageListView.RefreshInternal();
}
这样,在最终调用RefreshInternal()之前,它会再检查一次。

可能是lock(mImageListView){…}?

这里有一个。您最好只捕获ObjectDisposed异常并完成它。事实上,我认为在这种情况下,这是唯一有效的解决办法

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

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

通过在FormClosing事件中设置e.Cancel并通过ManualReset事件向线程发送停止信号来执行此操作。线程完成后,再次调用Form.Close()。使用BackgroundWorker可以轻松实现线程完成逻辑,请在中查找示例代码。

您可以使用互斥体

线程开始时的某个位置:

 Mutex m=new Mutex();
然后:

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

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

    m.ReleaseMutex();
}
无论您在何处处理mImageListView:

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

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

另请参见此问题:

生成的实用程序类可以解决事件方法签名的此问题。您可以修改这个类或查看其中的逻辑来解决这个问题


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

如果可能有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");
    }
}

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

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

Isak Savo提出的解决方案

try
  {
  myForm.Invoke(myForm.myDelegate, new Object[] { message });
  }
catch (ObjectDisposedException)
  { //catch exception if the owner window is already closed
  }
在C#4.0中工作,但由于某些原因,它在C#3.0中失败(无论如何都会引发异常)

因此,我使用了另一种基于标志的解决方案,该标志指示表单是否正在关闭,因此,如果设置了该标志,则禁止使用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 });
  }
这样做的好处是完全避免使用try/catch。

尝试使用

if(!myControl.Disposing)
    ; // invoke here
我和你的问题完全一样。自从我切换到控件上的check.Disposing之后,ObjectDisposedException就消失了。不要说这会100%地修复它,只有99%;)在Disposing的检查和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;
    }

停止生成消息的线程的建议是不可接受的。代理可以是多播的。因为一个听众不想听乐队的音乐,所以你不能向乐队成员开枪。 由于框架没有提供我所知的任何简单方法来清除这些事件消息的消息泵,并且由于表单没有公开其私有属性,从而让我们知道表单正在关闭:
取消订阅或停止侦听事件后,在窗口的IsClosing事件上设置一个标志,并在执行this.Invoke()之前始终检查该标志。

事实是,使用Invoke和friends,您无法完全防止对已处置组件的调用,或者由于缺少句柄而获取InvalidOperationException。我还没有在任何线程中看到解决真正根本问题的答案,就像下面的答案一样,这些问题不能通过抢占式测试或使用锁语义来完全解决

以下是正常的“正确”成语:

// the event handler. in this case preped for cross thread calls  
void OnEventMyUpdate(object sender, MyUpdateEventArgs e)
{
    if (!this.IsHandleCreated) return;  // ignore events if we arn't ready, and for
                                        // invoke if cant listen to msg queue anyway
    if (InvokeRequired) 
        Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData);
    else
        this.MyUpdate(e.MyData);
}

// the update function
void MyUpdate(Object myData)
{
    ...
}

基本问题:

在使用调用工具时,使用windows消息队列,它将消息放置在队列中,以等待或触发并忘记跨线程调用,与Post或Send消息完全相同。如果在调用消息之前有一条消息将使组件及其窗口句柄无效,或者是在您尝试执行任何检查之后才放置的消息,那么您将度过一段糟糕的时光

 x thread -> PostMessage(WM_CLOSE);   // put 'WM_CLOSE' in queue
 y thread -> this.IsHandleCreated     // yes we have a valid handle
 y thread -> this.Invoke();           // put 'Invoke' in queue
ui thread -> this.Destroy();          // Close processed, handle gone
 y thread -> throw Invalid....()      // 'Send' comes back, thrown on calling thread y
没有真正的方法知道控件将要从队列中移除自己,也没有真正合理的方法可以“撤消”调用。无论你做了多少次检查或额外锁定,你都无法阻止其他人发出关闭或停用之类的消息。有很多塞纳里奥人会发生这种情况

解决方案:

首先要意识到的是调用将失败,这与(IsHandleCreated)检查忽略事件的方式没有什么不同。如果目标是保护非UI线程上的调用方,则您需要处理异常,并将其视为任何其他未成功的调用(以防止应用程序崩溃或执行任何操作)。除非要重写/重新滚动调用工具,否则捕获是您唯一知道的方法

// the event handler. in this case preped for cross thread calls  
void OnEventMyWhatever(object sender, MyUpdateEventArgs e)
{
    if (!this.IsHandleCreated) return;
    if (InvokeRequired) 
    {
        try
        {
            Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData);
        }
        catch (InvalidOperationException ex)    // pump died before we were processed
        {
            if (this.IsHandleCreated) throw;    // not the droids we are looking for
        }
    }
    else
    {
        this.MyUpdate(e.MyData);
    }
}

// the update function
void MyUpdate(Object myData)
{
    ...
}
异常过滤可以根据需要进行定制。在大多数应用程序中,工作线程通常不具备UI线程所具备的所有轻松的外部异常处理和日志记录功能,因此您可能希望在工作线程端吞掉任何异常。或者记录并重新记录所有异常
// the event handler. in this case preped for cross thread calls  
void OnEventMyWhatever(object sender, MyUpdateEventArgs e)
{
    if (!this.IsHandleCreated) return;
    if (InvokeRequired) 
    {
        try
        {
            Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData);
        }
        catch (InvalidOperationException ex)    // pump died before we were processed
        {
            if (this.IsHandleCreated) throw;    // not the droids we are looking for
        }
    }
    else
    {
        this.MyUpdate(e.MyData);
    }
}

// the update function
void MyUpdate(Object myData)
{
    ...
}
public bool IsDisposed(Control ctrl)
{
    if (ctrl.IsDisposed)
        return true;
    try
    {
        ctrl.Invoke(new Action(() => { }));
        return false;
    }
    catch (ObjectDisposedException)
    {
        return true;
    }
}
if (this.IsHandleCreated){
    Task.Delay(500).ContinueWith(_ =>{
        this.Invoke(fm2);
    });
} else {
  this.Refresh();
}