我有一个类似于Greg D discusses here的SafeInvoke Control扩展方法(减去IsHandleCreated检查)。
我是从System.Windows.Forms.Form
调用它,如下所示:
public void Show(string text) {
label.SafeInvoke(()=>label.Text = text);
this.Show();
this.Refresh();
}
有时(此调用可能来自各种线程)这会导致以下错误:
发生了
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
发生了什么,我该如何解决?我知道这不是表单创建的问题,因为有时候它会工作一次并且下一次失败,那么问题是什么呢?
PS。我真的很擅长WinForms,有没有人知道一系列很好的文章解释整个模型以及如何使用它?
答案 0 :(得分:68)
您可能在错误的线程上创建控件。请考虑以下documentation from MSDN:
这意味着InvokeRequired可以 如果不需要Invoke,则返回false (调用发生在同一个线程上), 或如果控件是在a上创建的 不同的线程,但控件的 句柄尚未创建。
在控件的句柄中 尚未创建,你应该 不是简单地调用属性,方法, 或控件上的事件。这有可能 导致控件的句柄 在后台线程上创建, 隔离线程上的控件 没有消息泵和制作 应用不稳定。
您可以通过以下方式防范此情况 还要检查的价值 InvokeRequired时IsHandleCreated 在后台线程上返回false。 如果还没有控制手柄 创建,你必须等到它 在调用Invoke之前创建的 BeginInvoke的。通常,这会发生 仅在创建后台线程时 在主窗体的构造函数中 对于应用程序(如在 Application.Run(new MainForm()), 在表格显示之前或 已经调用了Application.Run。
让我们看看这对你意味着什么。 (如果我们看到你的SafeInvoke实现也会更容易推理)
假设您的实现与引用的实现相同,但对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();
}
}
考虑我们从非gui线程调用SafeInvoke
来获取尚未创建句柄的控件的情况。
uiElement
不为空,因此我们检查uiElement.InvokeRequired
。根据MSDN文档(粗体)InvokeRequired
将返回false
,因为即使它是在不同的线程上创建的,也没有创建句柄!这会将我们转到else
条件,我们会检查IsDisposed
或立即从后台主题调用提交的操作... !
此时,所有的赌注都是关闭的:那个控制因为它的句柄是在一个没有消息泵的线程上创建的,如第二段所述。也许这就是你遇到的情况?
答案 1 :(得分:35)
我发现InvokeRequired
不可靠,所以我只是使用
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
答案 2 :(得分:18)
以下是answer question的类似http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html:
我认为(尚未完全确定) 这是因为InvokeRequired会 如果控件有,则总是返回false 尚未加载/显示。我已经做好了 似乎有用的解决方法 那一刻,这很简单 引用相关的句柄 在其创建者中控制,如下:
var x = this.Handle;
(参见 {{3}})
答案 3 :(得分:5)
链接到的帖子中的方法调用Invoke / BeginInvoke,然后检查是否已从未创建控件的线程调用控件的句柄。
因此,当您从创建控件的线程以外的线程调用方法时,您将获得异常。这可能发生在远程事件或排队的工作用户项目......
修改强>
如果在调用invoke之前检查InvokeRequired和HandleCreated,则不应该得到该异常。
答案 4 :(得分:3)
如果您要在使用Control
显示或执行其他操作之前使用其他线程中的Control
,请考虑在构造函数中强制创建其句柄。这是使用CreateHandle
函数完成的。
在多线程项目中,“控制器”逻辑不在WinForm中,此函数在Control
构造函数中有助于避免此错误。
答案 5 :(得分:1)
在其创建者中引用关联控件的句柄,如下所示:
注意:警惕这个解决方案。如果一个控件有一个句柄,那么设置它的大小和位置就会慢得多。这使 InitializeComponent 慢得多。一个更好的解决方案是在控件有句柄之前不要背景。
答案 6 :(得分:0)
我有这种简单形式的问题:
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; }));
}
}
然后对于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; });
}
}
我猜它到目前为止工作正常......
答案 7 :(得分:0)
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();
答案 8 :(得分:0)
在调用方法调用while (!this.IsHandleCreated)
System.Threading.Thread.Sleep(100)
答案 9 :(得分:0)
那呢:
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;
}