我的工作线程中有以下代码(下面的ImageListView
来自Control
):
if (mImageListView != null &&
mImageListView.IsHandleCreated &&
!mImageListView.IsDisposed)
{
if (mImageListView.InvokeRequired)
mImageListView.Invoke(
new RefreshDelegateInternal(mImageListView.RefreshInternal));
else
mImageListView.RefreshInternal();
}
但是,我有时会使用ObjectDisposedException
方法获得Invoke
。看来控件可以在我检查IsDisposed
和拨打Invoke
之间处理。我怎么能避免这种情况?
答案 0 :(得分:19)
这里有race condition。你最好只是捕获ObjectDisposed异常并完成它。事实上,我认为在这种情况下它是唯一的工作解决方案。
try
{
if (mImageListView.InvokeRequired)
mImageListView.Invoke(new YourDelegate(thisMethod));
else
mImageListView.RefreshInternal();
}
catch (ObjectDisposedException ex)
{
// Do something clever
}
答案 1 :(得分:14)
您的代码中存在隐含的竞争条件。控件可以在IsDisposed测试和InvokeRequired测试之间进行处理。 InvokeRequired和Invoke()之间还有另一个。如果不确保控件的使用寿命超过线程的寿命,则无法解决此问题。鉴于您的线程正在为列表视图生成数据,它应该在列表视图消失之前停止运行。
通过在FormClosing事件中设置e.Cancel并通过ManualResetEvent指示线程停止来执行此操作。线程完成后,再次调用Form.Close()。使用BackgroundWorker可以轻松实现线程完成逻辑,在this post中找到示例代码。
答案 2 :(得分:3)
尝试使用
if(!myControl.Disposing)
; // invoke here
我和你有完全相同的问题。自从我切换到检查.Disposing控件后,ObjectDisposedException就消失了。不是说这会在100%的时间里修复它,只有99%;)在检查到Disposing和调用调用之间仍然存在竞争条件的可能性,但在测试中我已经完成了我没有运行进入它(我使用ThreadPool和工作线程)。
这是我在每次调用调用之前使用的内容:
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;
}
答案 3 :(得分:3)
现实情况是,使用Invoke和朋友,您无法完全防止对已处置组件的调用,或者由于缺少句柄而导致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)
{
...
}
这个基本问题:
在使用Invoke工具时,会使用Windows消息队列,它会在队列中放置一条消息,以便等待或发送 - 忘记交叉线程调用,就像发布或发送消息一样。如果在Invoke消息之前有一条消息将使组件及其窗口句柄无效,或者在您尝试执行的任何检查之后放置该消息,那么您将会遇到错误的时间。
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线程上的调用者,则需要处理异常,并将其视为未成功的任何其他调用(以防止应用程序崩溃或执行任何操作。除非要重写/ reroll Invoke facility,catch是你唯一知道的方式。
// 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线程,在大多数应用程序中,所以你可能希望只是吞噬工人端的任何异常。或者记录并重新抛出所有这些。对于许多人来说,工作线程上未被捕获的异常意味着应用程序将崩溃。
答案 4 :(得分:1)
可能是lock(mImageListView){...}?
答案 5 :(得分:1)
您可以使用互斥锁。
线程开头的某处:
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();
这应该确保你不能同时处理和调用。
答案 6 :(得分:1)
另见这个问题:
Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?
生成EventHandlerForControl的实用程序类可以解决事件方法签名的此问题。您可以调整此课程或查看其中的逻辑来解决问题。
这里真正的问题是nobugz是正确的,因为他指出在winforms中为跨线程调用提供的API本质上不是线程安全的。即使在对InvokeRequired和Invoke / BeginInvoke本身的调用中,也有一些可能导致意外行为的竞争条件。
答案 7 :(得分:1)
如果有可能使用BackGroundWorker,那么有一种非常simple的方法来规避这一点:
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");
}
}
答案 8 :(得分:1)
处理表单结束事件。检查你的off UI线程是否仍在工作,如果是这样,开始将其关闭,取消关闭事件,然后使用表单控件上的BeginInvoke重新安排关闭。
private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
if (service.IsRunning)
{
service.Exit();
e.Cancel = true;
this.BeginInvoke(new Action(() => { this.Close(); }));
}
}
答案 9 :(得分:1)
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中失败(无论如何都会引发异常)
所以我使用另一个解决方案,该解决方案基于一个标志,指示表单是否正在关闭,因此如果设置了标志则阻止使用调用
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。
答案 10 :(得分:0)
一种方法可能是调用方法本身而不是调用ImageListView-Method:
if (mImageListView != null &&
mImageListView.IsHandleCreated &&
!mImageListView.IsDisposed)
{
if (mImageListView.InvokeRequired)
mImageListView.Invoke(new YourDelegate(thisMethod));
else
mImageListView.RefreshInternal();
}
这样它会在最终调用RefreshInternal()之前再检查一次。
答案 11 :(得分:0)
停止生成消息的线程的建议是不可接受的。代表可以进行多播。因为一个听众不想听乐队,你不会拍乐队成员。 由于框架没有提供任何简单的方法来清除这些事件消息的消息泵,并且由于表单没有公开其私有属性,让我们知道表单正在关闭: 在取消订阅或停止收听事件后,在窗口的IsClosing事件上设置一个标志,并在执行此操作之前始终检查此标志.Invoke()。
答案 12 :(得分:0)
我有同样的错误。我的错误发生在线程中。最后我写了这个方法:
public bool IsDisposed(Control ctrl)
{
if (ctrl.IsDisposed)
return true;
try
{
ctrl.Invoke(new Action(() => { }));
return false;
}
catch (ObjectDisposedException)
{
return true;
}
}
答案 13 :(得分:0)
这对我有用
if (this.IsHandleCreated){
Task.Delay(500).ContinueWith(_ =>{
this.Invoke(fm2);
});
} else {
this.Refresh();
}