避免在处理控件时调用Invoke我的工作线程中有以下代码(下面的ImageListView派生自Control):if(mImageListView!=null&&mImageListView.IsHandleCreated&&!mImageListView.IsDisposed){如果(mImageListView.InvokeRequired)mImageListView.Invoke(newRefreshDelegateInternal(mImageListView.RefreshInternal));否则mImageListView.RefreshInternal();但是,我有时会使用上面的Invoke方法得到ObjectDisposedException。当我检查IsDisposed并调用Invoke时,该控件似乎可用。我怎样才能避免这种情况?您的代码中存在隐式竞争条件。可以在IsDisposed测试和InvokeRequired测试之间部署控件。在InvokeRequired和Invoke()之间还有一个。如果不确保控件超过线程的生命周期,则无法解决此问题。鉴于您的线程正在为列表视图生成数据,它应该在列表视图消失之前停止运行。通过在FormClosing事件中设置e.Cancel并通过ManualResetEvent发出停止信号来执行此操作。线程完成后,再次调用Form.Close()。可以使用BackgroundWorker轻松实现线程完成逻辑,请在本文中找到示例代码。你在这里有一个竞争条件。您最好只捕获ObjectDisposedexception并完成它。事实上,我认为这是这种情况下唯一可行的解??决方案。尝试{如果(mImageListView.InvokeRequired)mImageListView.Invoke(newYourDelegate(thisMethod));否则mImageListView.RefreshInternal();}catch(ObjectDisposedExceptionex){//做点聪明的事}tryif(!myControl.Disposing);//在这里调用我遇到了和你完全一样的问题。因为我切换到检查.Disposing控件,所以ObjectDisposedException消失了。并不是说这会在100%的时间内修复它,只有99%;)在检查处理和调用调用之间仍然存在竞争条件的可能性,但在我完成的测试中我没有遇到它(我使用ThreadPool和工作线程)。这是我在每次调用之前使用的内容:privateboolIsControlValid(ControlmyControl){if(myControl==null)returnfalse;如果(myControl.IsDisposed)返回false;如果(myControl.Disposing)返回false;如果(!myControl.IsHandleCreated)返回false;如果(中止线程)返回假;//给线程停止处理的信号returntrue;现实情况是,使用Invoke和朋友,您不能完全阻止对已处置组件的调用,或者由于缺少句柄而导致的InvalidOperationException。我还没有真正在任何线程中看到像下面这样的答案,它解决了抢先测试或使用锁语义没有完全解决的真正基本问题。这是正常的“正确”习语://事件处理程序。在这种情况下,为跨线程调用做准备voidOnEventMyUpdate(objectsender,MyUpdateEventArgse)//如果我们还没有准备好,则忽略事件,如果(InvokeRequired)Invoke(newMyUpdateCallback(this.MyUpdate),e.MyData);//如果无法收听消息队列,则调用否则this.MyUpdate(e.MyData);}//更新函数voidMyUpdate(ObjectmyData){...}最基本的问题:在使用Invoke工具时,使用windows消息队列,将消息放入队列等待或发送忘记跨线程调用,就像发布或发送消息一样。如果您在Invoke消息之前有一条消息会使组件及其窗口句柄无效,或者将其放在您尝试执行的任何检查之后,那么您将有错误的时机。x线程->PostMessage(WM_CLOSE);//将'WM_CLOSE'放入队列y线程->this.IsHandleCreated//是的,我们有一个有效的句柄y线程->this.Invoke();//将'Invoke'放入队列uithread->this.Destroy();//Closeprocessed,handlegoneythread->throwInvalid....()//'Send'返回,在调用线程上抛出y没有真正的方法知道控件即将从队列中移除,并且没有什么可以“撤消”呼叫。无论您进行了多少次检查或进行了多少次额外的锁定,您都无法阻止其他人发出诸如关闭或停用之类的命令。在很多情况下都可能发生这种情况。一种方式:首先要意识到调用将失败,这与(IsHandleCreated)检查忽略事件的方式没有什么不同。如果目标是保护非UI线程上的调用者,则需要处理异常,并将其视为任何其他未成功的调用(以防止应用程序崩溃或执行任何操作。除非你想覆盖/重新启动Invoke工具,catch是您知道的唯一方法。//事件处理程序。在这种情况下,为跨线程调用做准备InvokeRequired){try{Invoke(newMyUpdateCallback(this.MyUpdate),e.MyData);}catch(InvalidOperationExceptionex)//泵在我们被处理之前就死了{if(this.IsHandleCreated)throw;//我们不是机器人lookingfor}}else{this.MyUpdate(e.MyData);}}//更新函数voidMyUpdate(ObjectmyData){...}可以自定义异常过滤以满足任何需求。很高兴知道工作线程通常在大多数应用程序中,UI线程上没有所有简单的外部异常处理和日志记录,因此您可能只想吞下工作端的任何异常。或者记录并重新抛出它们。对于许多人来说,工作线程上的未捕获异常意味着应用程序将崩溃。也许锁定(mImageListView){...}?您可以使用互斥体。在线程开始的某处:Mutexm=newMutex();然后:if(mImageListView!=null&&mImageListView.IsHandleCreated&&!mImageListView.IsDisposed){m.WaitOne();如果(mImageListView.InvokeRequired)mImageListView.Invoke(newRefreshDelegateInternal(mImageListView.RefreshInternal));否则mImageListView.RefreshInternal();m.ReleaseMutex();}你是否正在处理mImageListView:m.WaitOne();mImageListView.Dispose();m.ReleaseMutex();这个要保证不能同时配置和调用。另请参阅以下问题:AvoidingtheInvoke/BeginInvokedilemmaincross-threadedWinFormeventhandling?生成EventHandlerForControl的实用程序类可以使用事件方法签名解决此问题。您可以调整此类或查看其中的逻辑来解决问题。这里真正的问题是nobugz正确地指出了winforms中为跨线程调用提供的API本身并不是线程安全的。即使在对InvokeRequired和Invoke/BeginInvoke的调用中,也存在一些可能导致意外行为的竞争条件。如果BackGroundWorker是可能的,有一个非常简单的方法来规避这个:publicpartialclassMyForm:Form{privatevoidInvokeViaBgw(Actionaction){BGW.ReportProgress(0,action);}privatevoidBGW_ProgressChanged(objectsender,ProgressChangedEventArgse){if(this.IsDisposed)return;//你现在在UI线程上,所以没有竞争条件varaction=(Action)e.UserState;行动();}privateprivatevoidBGW_DoWork(objectsender,DoWorkEventArgse){//示例用法:this.InvokeViaBgw(()=>MyTextBox.Text="Foo");}}处理表单结束活动。检查您的关闭UI线程是否仍在工作,如果是,则开始关闭它,取消关闭事件,然后使用表单控件上的BeginInvoke重新安排关闭。privatevoidForm_FormClosing(objectsender,FormClosingEventArgse){if(service.IsRunning){service.Exit();e.Cancel=true;this.BeginInvoke(newAction(()=>{this.Close();}));}}IsakSavo提出的解决方案try{myForm.Invoke(myForm.myDelegate,newObject[]{message});}catch(ObjectDisposedException){//catchexceptioniftheownerwindowisalreadyclosed}在C#4.0中,但由于某种原因它在C#3.0中失败(无论如何都会抛出异常)所以我使用另一个基于标志的解决方案,该标志指示是否表单是否正在关闭,所以如果设置了标志,则阻止使用公共部分类Form1:Form{bool_closing;publicboolclosing{get{return_closing;}}privatevoidForm1_FormClosing(objectsender,FormClosingEventArgse){_closing=true;}...//部分在另一个线程中执行:if(_owner.closing==false){//如果表单正在关闭,调用将被跳过myForm.Invoke(myForm.myDelegate,newObject[]{message});}这具有完全避免try/catch的优点。一种方法是调用方法本身而不是调用ImageListView-Method:;否则mImageListView.RefreshInternal();}以便它在最终调用RefreshInternal()之前再次检查。停止线程生成消息的建议是不可接受的。代表可以多播。因为一个听众不想听乐队,所以你不拍乐队成员。由于框架没有提供任何简单的方法来清除这些事件消息的消息泵,并且由于表单没有公开其私有属性来让我们知道表单正在关闭:在IsClosing事件上设置一个标志以取消订阅或在window之后停止监听事件,并在执行this.Invoke()之前始终检查此标志。我有同样的错误。我的错误发生在线程中。最后我写了这个方法:publicboolIsDisposed(Controlctrl){if(ctrl.IsDisposed)returntrue;尝试{ctrl.Invoke(newAction(()=>{}));返回假;}catch(ObjectDisposedException){返回真;这对我有用以上是C#学习教程:避免在处理控件时调用Invoke。如果对大家有用,需要进一步了解C#学习教程,希望大家多多关注——if(this.IsHandleCreated){Task.Delay(500).ContinueWith(_=>{this.Invoke(fm2);});}else{this.Refresh();}本文收集自网络,不代表立场。侵权请点击右侧联系管理员删除。如需转载请注明出处:
