C#Winforms线程:调用Closed Form

以下代码说明了我的困境。 代码创建一个处理内容的后台线程,然后使用结果调用UI线程。

如果后台线程在窗体关闭后调用窗体上的Invoke,它可能会抛出exception。 它在调用Invoke之前检查IsHandleCreated,但是在检查之后表单可能会关闭。

void MyMethod() { // Define background thread Action action = new Action( () => { // Process something var data = BackgroundProcess(); // Try to ensure the form still exists and hope // that doesn't change before Invoke is called if (!IsHandleCreated) return; // Send data to UI thread for processing Invoke(new MethodInvoker( () => { UpdateUI(data); })); }); // Queue background thread for execution action.BeginInvoke(); } 

一种解决方案可能是同步FormClosing和每次调用Invoke,但这听起来不是很优雅。 有没有更简单的方法?

是的,这里有一场比赛。 在目标开始运行之前,A需要很长的时间。 如果使用Control.BeginInvoke(),它将更好地工作,表单的Dispose()实现将清空调度队列。 但这仍然是一场比赛,尽管它很少会发生。 您在代码段中编写的代码不需要Invoke()。

唯一干净的解决方法是将FormClosing事件联锁并延迟关闭,直到您确认后台线程已完成且无法再次启动。 您的代码不容易,因为这需要“完成”回调,因此您可以真正关闭表单。 BackgroundWorker将是一个更好的捕鼠器 。 Q&D修复是捕获BeginInvoke将引发的ObjectDisposedException。 鉴于使用BeginInvoke()时这种情况有多么罕见,这种丑陋的黑客行为是可以接受的。 你只是无法测试它:)

我通过使用Hans Passant建议捕获ObjectDisposedException来解决BeginInvoke的这个同步问题。 到目前为止,它似乎工作。 我创建了Control类的扩展方法来实现这一点。

TryBeginInvoke尝试在控件上调用自己的方法。 如果成功调用该方法,它将检查控件是否已被释放。 如果已经处理,它会立即返回; 否则,它将最初作为参数传递给TryBeginInvoke的方法调用。 代码如下:

 public static class ControlExtension { // --- Static Fields --- static bool _fieldsInitialized = false; static InvokeDelegateDelegate _methodInvokeDelegate; // Initialized lazily to reduce application startup overhead [see method: InitStaticFields] static InvokeMethodDelegate _methodInvokeMethod; // Initialized lazily to reduce application startup overhead [see method: InitStaticFields] // --- Public Static Methods --- public static bool TryBeginInvoke(this Control control, Delegate method, params object[] args) { IAsyncResult asyncResult; return TryBeginInvoke(control, method, out asyncResult, args); } /// May return true even if the target of the invocation cannot execute due to being disposed during invocation. public static bool TryBeginInvoke(this Control control, Delegate method, out IAsyncResult asyncResult, params object[] args) { if (!_fieldsInitialized) InitStaticFields(); asyncResult = null; if (!control.IsHandleCreated || control.IsDisposed) return false; try { control.BeginInvoke(_methodInvokeDelegate, control, method, args); } catch (ObjectDisposedException) { return false; } catch (InvalidOperationException) // Handle not created { return false; } return true; } public static bool TryBeginInvoke(this Control control, MethodInvoker method) { IAsyncResult asyncResult; return TryBeginInvoke(control, method, out asyncResult); } /// May return true even if the target of the invocation cannot execute due to being disposed during invocation. public static bool TryBeginInvoke(this Control control, MethodInvoker method, out IAsyncResult asyncResult) { if (!_fieldsInitialized) InitStaticFields(); asyncResult = null; if (!control.IsHandleCreated || control.IsDisposed) return false; try { control.BeginInvoke(_methodInvokeMethod, control, method); } catch (ObjectDisposedException) { return false; } catch (InvalidOperationException) // Handle not created { return false; } return true; } // --- Private Static Methods --- private static void InitStaticFields() { _methodInvokeDelegate = new InvokeDelegateDelegate(InvokeDelegate); _methodInvokeMethod = new InvokeMethodDelegate(InvokeMethod); } private static object InvokeDelegate(Control control, Delegate method, object[] args) { if (!control.IsHandleCreated || control.IsDisposed) return null; return method.DynamicInvoke(args); } private static void InvokeMethod(Control control, MethodInvoker method) { if (!control.IsHandleCreated || control.IsDisposed) return; method(); } // --- Private Nested Types --- delegate object InvokeDelegateDelegate(Control control, Delegate method, object[] args); delegate void InvokeMethodDelegate(Control control, MethodInvoker method); } 

看一下WindowsFormsSynchronizationContextPost方法在UI线程上调用您的UpdateUI委托,而不需要专用窗口; 这可以让你跳过调用IsHandleCreatedInvoke

编辑: MSDN在“使用基于事件的异步模式的multithreading编程”下有一些代码示例。

您可能会发现通过位于WindowsFormsSynchronizationContext之上的AsyncOperationManager类更容易编程。 反过来, BackgroundWorker组件构建在AsyncOperationManager之上。

UI线程定义为您调用AsyncOperationManager.CreateOperation线程; 你想在MyMethod的开头调用CreateOperation ,当你知道你在UI线程上,并在局部变量中捕获它的返回值。

您可以在调用之前检查表单(或任何控件)上的IsDisposed。

如果在同时处理表单的情况下,您还应该在您正在调用的实际方法内部进行检查。