C# Winforms 线程:调用封闭表单
下面的代码演示了我的困境。该代码创建一个后台线程来处理某些内容,然后使用结果调用 UI 线程。
如果后台线程在窗体关闭后调用窗体上的 Invoke,则可能会引发异常。它在调用 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,但这听起来不太优雅。有更简单的方法吗?
The following code demonstrates my dilemma. The code creates a background thread which processes something, then Invokes the UI thread with the result.
It may throw an exception if the background thread calls Invoke on the form after the form has closed. It checks IsHandleCreated before calling Invoke, but the form might close after the check.
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();
}
One solution might be to synchronize FormClosing and every call to Invoke, but that doesn't sound very elegant. Is there an easier way?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我通过使用 Hans Passant 的建议捕获 ObjectDisposeException 解决了 BeginInvoke 的同步问题。到目前为止,它似乎有效。我创建了 Control 类的扩展方法来促进这一点。
TryBeginInvoke 尝试在控件上调用它自己的方法。如果该方法被成功调用,它将检查该控件是否已被释放。如果已处理,则立即返回;否则,它调用最初作为参数传递给 TryBeginInvoke 的方法。代码如下:
I solved this synchronization issue for BeginInvoke by using Hans Passant's recommendation to catch the ObjectDisposedException. So far, it appears to work. I created extension methods of the Control class to facilitate this.
TryBeginInvoke attempts to invoke its own method on the control. If the method is successfully invoked, it checks whether the control has been disposed. If it has been disposed, it returns immediately; otherwise, it calls the method originally passed as a parameter to TryBeginInvoke. The code is as follows:
看一下
WindowsFormsSynchronizationContext
。Post
方法将调用发布到 UI 线程上的UpdateUI
委托,而无需专用窗口;这可以让您跳过调用IsHandleCreated
和Invoke
。编辑: MSDN 在"多线程编程下有一些代码示例使用基于事件的异步模式”。
您可能会发现通过位于
WindowsFormsSynchronizationContext
之上的AsyncOperationManager
类进行编程更容易。反过来,BackgroundWorker
组件构建在AsyncOperationManager
之上。UI 线程被定义为您调用 AsyncOperationManager.CreateOperation 的线程;当您知道自己位于 UI 线程上时,您希望在
MyMethod
开始时调用CreateOperation
,并在局部变量中捕获其返回值。Take a look at
WindowsFormsSynchronizationContext
. ThePost
method posts call to yourUpdateUI
delegate on the UI thread without needing a dedicated window; this lets you skip callingIsHandleCreated
andInvoke
.Edit: MSDN has some code examples under "Multithreaded Programming with the Event-based Asynchronous Pattern".
You might find it easier to program via the
AsyncOperationManager
class, which sits on top ofWindowsFormsSynchronizationContext
. In turn, theBackgroundWorker
component is built on top ofAsyncOperationManager
.The UI thread is defined as the one on which you call
AsyncOperationManager.CreateOperation
; you want to callCreateOperation
at the start ofMyMethod
, when you know you're on the UI thread, and capture its return value in a local variable.您可以在调用之前检查表单(或任何控件)上的 IsDispose。
您还应该在您调用的实际方法中检查这一点,以防表单同时被处理。
You can check IsDisposed on the form (or any control) before Invoking on it.
You should also check this inside of the actual method you're Invoking, in case the form was disposed in the meantime.
是的,这里有一场比赛。 A 在目标开始运行之前需要花费好几毫秒的时间。如果您使用 Control.BeginInvoke() 代替,它会工作得“更好”,表单的 Dispose() 实现将清空调度队列。但这仍然是一场竞赛,尽管这种情况很少发生。代码片段中编写的代码不需要 Invoke()。
唯一干净的修复方法是互锁 FormClosing 事件并延迟关闭,直到您确认后台线程已完成且无法再次启动。按原样处理您的代码并不容易,因为这需要“完成”回调,以便您可以真正关闭表单。 BackgroundWorker 会是一个更好的捕鼠器。 Q&D 修复是捕获 BeginInvoke 将引发的 ObjectDisposeException。考虑到当您使用 BeginInvoke() 时这种情况很少见,这种丑陋的黑客行为是可以接受的。你只是无法测试它:)
Yes, there's a race here. A takes a good millisecond before the target starts running. It will work 'better' if you use Control.BeginInvoke() instead, the form's Dispose() implementation will empty the dispatch queue. But that's still a race, albeit that it will strike very rarely. Your code as written in the snippet doesn't require Invoke().
The only clean fix is to interlock the FormClosing event and to delay the close until you got confirmation that the background thread is completed and can't be started again. Not easy to do with your code as is since that requires a 'completed' callback so you can really get the form closed. BackgroundWorker would be a better mousetrap. The Q&D fix is to catch the ObjectDisposedException that BeginInvoke will raise. Given how rare this will be when you use BeginInvoke(), that ugly hack could be acceptable. You just can't test it :)