multithreading 在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/808867/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
提问by George Mauer
I have a SafeInvoke Control extension method similar to the one Greg D discusses here(minus the IsHandleCreated check).
我有一个类似于Greg D 在这里讨论的 SafeInvoke Control 扩展方法(减去 IsHandleCreated 检查)。
I am calling it from a System.Windows.Forms.Form
as follows:
我从 a 调用它System.Windows.Forms.Form
如下:
public void Show(string text) {
label.SafeInvoke(()=>label.Text = text);
this.Show();
this.Refresh();
}
Sometimes (this call can come from a variety of threads) this results in the following error:
有时(此调用可能来自多个线程)这会导致以下错误:
System.InvalidOperationException
occurred
Message
= "Invoke or BeginInvoke cannot be called on a control until the window handle has been created."
Source
= "System.Windows.Forms"StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16
System.InvalidOperationException
发生了
Message
= "在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。"
Source
= "System.Windows.Forms"StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16
What is going on and how do I fix it? I know as much as it is not a problem of form creation, since sometimes it will work once and fail the next time so what could the problem be?
这是怎么回事,我该如何解决?我知道这不是表单创建的问题,因为有时它会工作一次而下一次失败,那么问题是什么?
PS. I really really am awful at WinForms, does anyone know a good series of articles that explains the whole model and how to work with it?
附注。我真的很擅长 WinForms,有没有人知道一系列解释整个模型以及如何使用它的好文章?
采纳答案by Greg D
It's possible that you're creating your controls on the wrong thread. Consider the following documentation from MSDN:
您可能在错误的线程上创建控件。考虑来自 MSDN的以下文档:
This means that InvokeRequired can return false if Invoke is not required (the call occurs on the same thread), or if the control was created on a different thread but the control's handle has not yet been created.
In the case where the control's handle has not yet been created, you should not simply call properties, methods, or events on the control. This might cause the control's handle to be created on the background thread, isolating the control on a thread without a message pump and making the application unstable.
You can protect against this case by also checking the value of IsHandleCreated when InvokeRequired returns false on a background thread. If the control handle has not yet been created, you must wait until it has been created before calling Invoke or BeginInvoke. Typically, this happens only if a background thread is created in the constructor of the primary form for the application (as in Application.Run(new MainForm()), before the form has been shown or Application.Run has been called.
这意味着如果不需要 Invoke(调用发生在同一线程上),或者如果控件是在不同的线程上创建但控件的句柄尚未创建,则 InvokeRequired 可以返回 false 。
在尚未创建控件句柄的情况下,不应简单地调用控件上的属性、方法或事件。这可能会导致在后台线程上创建控件的句柄,在没有消息泵的线程上隔离控件并使应用程序不稳定。
当 InvokeRequired 在后台线程上返回 false 时,您还可以通过检查 IsHandleCreated 的值来防止出现这种情况。如果尚未创建控制句柄,则必须等到它被创建后再调用 Invoke 或 BeginInvoke。通常,只有在显示窗体或调用 Application.Run 之前,在应用程序的主窗体的构造函数中创建了后台线程(如在 Application.Run(new MainForm()) 中)才会发生这种情况。
Let's see what this means for you. (This would be easier to reason about if we saw your implementation of SafeInvoke also)
让我们看看这对您意味着什么。(如果我们也看到您对 SafeInvoke 的实现,这将更容易推理)
Assuming your implementation is identical to the referenced one with the exception of the check against IsHandleCreated, let's follow the logic:
假设您的实现与引用的相同,但检查IsHandleCreated 除外,让我们遵循以下逻辑:
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
Consider the case where we're calling SafeInvoke
from the non-gui thread for a control whose handle has not been created.
考虑我们SafeInvoke
从非 gui 线程调用句柄尚未创建的控件的情况。
uiElement
is not null, so we check uiElement.InvokeRequired
. Per the MSDN docs (bolded) InvokeRequired
will return false
because, even though it was created on a different thread, the handle hasn't been created! This sends us to the else
condition where we check IsDisposed
or immediately proceed to call the submitted action... from the background thread!
uiElement
不为空,所以我们检查uiElement.InvokeRequired
. 根据 MSDN 文档(粗体)InvokeRequired
将返回,false
因为即使它是在不同的线程上创建的,也没有创建句柄!这会将我们发送到else
我们检查IsDisposed
或立即继续调用提交的操作的条件......从后台线程!
At this point, all bets are off re: that control because its handle has been created on a thread that doesn't have a message pump for it, as mentioned in the second paragraph. Perhaps this is the case you're encountering?
在这一点上,所有赌注都关闭了:该控制,因为它的句柄是在没有消息泵的线程上创建的,如第二段所述。也许您遇到的就是这种情况?
回答by Mathieu
I found the InvokeRequired
not reliable, so I simply use
我发现InvokeRequired
不可靠,所以我只是使用
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
回答by Benjol
Here is my answerto a similar question:
I think(not yet entirely sure) that this is because InvokeRequired will always return false if the control has not yet been loaded/shown. I have done a workaround which seems to work for the moment, which is to simple reference the handle of the associated control in its creator, like so:
var x = this.Handle;
(See http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html)
我认为(尚不完全确定)这是因为如果尚未加载/显示控件,则 InvokeRequired 将始终返回 false。我已经完成了一个目前似乎有效的解决方法,即在其创建者中简单地引用关联控件的句柄,如下所示:
var x = this.Handle;
(见 http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html)
回答by Shea
The method in the post you link to calls Invoke
/BeginInvoke
before checking if the control's handle has been created in the case where it's being called from a thread that didn't create the control.
您链接到的帖子中的方法调用Invoke
/BeginInvoke
之前检查控件的句柄是否已创建,如果它是从未创建控件的线程调用的。
So you'll get the exception when your method is called from a thread other than the one that created the control. This can happen from remoting events or queued work user items...
因此,当您的方法从创建控件的线程以外的线程调用时,您将收到异常。这可能发生在远程事件或排队的工作用户项目中...
EDIT
编辑
If you check InvokeRequired
and HandleCreated
before calling invoke you shouldn't get that exception.
如果在调用 invoke 之前检查InvokeRequired
并检查,则HandleCreated
不应出现该异常。
回答by Limited Atonement
If you're going to use a Control
from another thread before showing or doing other things with the Control
, consider forcing the creation of its handle within the constructor. This is done using the CreateHandle
function.
如果您要在使用Control
显示或执行其他操作之前使用另一个线程中的Control
,请考虑在构造函数中强制创建其句柄。这是使用该CreateHandle
函数完成的。
In a multi-threaded project, where the "controller" logic isn't in a WinForm, this function is instrumental in Control
constructors for avoiding this error.
在多线程项目中,“控制器”逻辑不在 WinForm 中,此函数在Control
构造函数中有助于避免此错误。
回答by amos godwin
Add this before you call method invoke:
在调用方法调用之前添加:
while (!this.IsHandleCreated)
System.Threading.Thread.Sleep(100)
回答by joe
Reference the handle of the associated control in its creator, like so:
在其创建者中引用关联控件的句柄,如下所示:
Note: Be wary of this solution.If a control has a handle it is much slower to do things like set the size and location of it. This makes InitializeComponentmuch slower. A better solution is to not background anything before the control has a handle.
注意:小心这个解决方案。如果一个控件有一个句柄,那么设置它的大小和位置等操作会慢得多。这使得InitializeComponent慢得多。更好的解决方案是在控件具有句柄之前不进行任何背景处理。
回答by Gourou Dsecours
What about this :
那这个呢 :
public static bool SafeInvoke( this Control control, MethodInvoker method )
{
if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
{
if( control.InvokeRequired )
{
control.Invoke( method );
}
else
{
method();
}
return true;
}
else return false;
}
回答by rupweb
I had this problem with this kind of simple form:
我有这种简单形式的问题:
public partial class MyForm : Form
{
public MyForm()
{
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(Object sender, EventArgs e)
{
InitializeComponent();
}
internal void UpdateLabel(string s)
{
Invoke(new Action(() => { label1.Text = s; }));
}
}
Then for n
other async threads I was using new MyForm().UpdateLabel(text)
to try and call the UI thread, but the constructor gives no handle to the UI thread instance, so other threads get other instance handles, which are either Object reference not set to an instance of an object
or Invoke or BeginInvoke cannot be called on a control until the window handle has been created
. To solve this I used a static object to hold the UI handle:
然后对于n
我new MyForm().UpdateLabel(text)
用来尝试调用 UI 线程的其他异步线程,但构造函数没有提供 UI 线程实例的句柄,因此其他线程获得其他实例句柄,它们是Object reference not set to an instance of an object
或Invoke or BeginInvoke cannot be called on a control until the window handle has been created
。为了解决这个问题,我使用了一个静态对象来保存 UI 句柄:
public partial class MyForm : Form
{
private static MyForm _mf;
public MyForm()
{
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(Object sender, EventArgs e)
{
InitializeComponent();
_mf = this;
}
internal void UpdateLabel(string s)
{
_mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; });
}
}
I guess it's working fine, so far...
我想它工作正常,到目前为止......
回答by Shimon Doodkin
var that = this; // this is a form
(new Thread(()=> {
var action= new Action(() => {
something
}));
if(!that.IsDisposed)
{
if(that.IsHandleCreated)
{
//if (that.InvokeRequired)
that.BeginInvoke(action);
//else
// action.Invoke();
}
else
that.HandleCreated+=(sender,event) => {
action.Invoke();
};
}
})).Start();