我有一个类试图创建一个更好的替代类
System.Console
类。事情是这样开始的:
public class SuperConsole
{
private readonly Form _Form;
public SuperConsole()
{
_Form = new()
{
Text = DefaultTitle,
BackColor = DefaultBackColor,
};
Application.Run(_Form);
}
public string Title
{
get => _Form.Invoke(() => _Form.Text);
set => _Form.Invoke(() => _Form.Text = value);
}
//more stuff
}
显然,如果我运行构造函数,它会冻结在
Application.Run
。当然,表单会运行,但构造函数的调用者必须稍等一下。因此,我更改了构造函数以在新线程上运行 Application.Run
:
public SmartConsoleWindow()
{
_Form = new()
{
Text = DefaultTitle,
BackColor = DefaultBackColor,
};
Thread t = new(() => Application.Run(_Form));
t.Start();
}
但这会引发
System.InvalidOperationException
。可能有充分的理由。我有一种不好的预感,如果在第二个线程开始弄清楚 Title
之前访问 Application.Run
属性会发生什么。
我不知道如何继续。我尝试了几种变体,但没有效果。
我希望表单和调用函数能够同时运行,并且仍然能够正常使用
Form.Invoke
。我还希望该类的用户不必担心这些。我该怎么做?
欢迎任何建议!
我认为你必须在专用的
Form
线程上创建 SuperConsole
,而不是在当前线程上,因为 UI 组件是线程仿射的。您还必须将 SuperConsole
线程声明为 STA,并等待表单创建。像这样的东西应该有效:
public class SuperConsole
{
private Form _form;
public SmartConsoleWindow()
{
ManualResetEventSlim mres = new();
Thread t = new(() =>
{
_form = new()
{
Text = DefaultTitle,
BackColor = DefaultBackColor,
};
mres.Set();
Application.Run(_form);
});
t.Name = "SuperConsole";
t.SetApartmentState(ApartmentState.STA);
t.Start();
mres.Wait();
}
public string Title
{
get => _form.Invoke(() => _Form.Text);
set => _form.Invoke(() => _Form.Text = value);
}
}
ManualResetEventSlim
用于表示 Form
实例已创建,并分配给 _form
字段。否则,当前线程可能会观察到 _form
为 null
。
我没有测试过上面的代码。在消息循环开始之前,表单可能尚未准备好调用
Invoke
。在这种情况下,您可能需要将 mres.Set();
移动到表单的 Load
或 Shown
事件内。要了解如何仅订阅一个通知的事件,请参阅此答案。