来自 TabControl 的非 UI 线程的 UI 调用处理不一致
Inconsistent handling of UI calls from non-UI thread for TabControl
这不是重复的。 Whosebug 上尚未探索 WinForms TabControl 的这种特殊行为。
查看下面的示例:
我在 .NET 4.0 中有一个带有两个选项卡的 TabControl。每个选项卡中都有一个 Label。当我单击 Button 时,我启动了 BackgroundWorker,它现在在非 UI 线程上运行。如果我尝试从 tabPage1 更改 Label,由于跨线程调用,我会收到 InvalidOperationException。但是修改 tabPage2 上的 Label 的第二行运行得非常好——没有例外。
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += Bgw_DoWork;
bgw.RunWorkerAsync();
}
private void Bgw_DoWork(object sender, DoWorkEventArgs e)
{
try
{
label1.Text = "Testing tabPage1"; // This is sitting on tabPage1 - THROWS CROSS THREAD OPERATION
label2.Text = "Testing tabPage2"; // This is sitting on tabPage2 - RUNS FINE
}
catch (Exception ex)
{
throw ex;
}
}
为什么这在 tabPage2 上允许但在 tabPage1 上不允许。在这两种情况下,我们似乎都在从非 UI 线程修改 UI。
This isControl.Text
setter属性:
set
{
// some code omitted
this.WindowText = value;
this.OnTextChanged(EventArgs.Empty);
// some code omitted
}
它只是转发到 Control.WindowText
属性。让我们检查一下:
set
{
if (value == null) value = "";
if (!WindowText.Equals(value)) {
if (IsHandleCreated) {
UnsafeNativeMethods.SetWindowText(new HandleRef(window, Handle), value);
}
else {
if (value.Length == 0) {
text = null;
}
else {
text = value;
}
}
}
}
InvalidOperationException
来源于Handle
属性getter,调用获取HandleRef
实例,用于native方法调用
get {
if (checkForIllegalCrossThreadCalls &&
!inCrossThreadSafeCall &&
InvokeRequired) {
throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall, Name));
}
// further code omitted
}
Handle
属性 只有在 IsHandleCreated
为 true
时才会被访问。在您的情况下,第二个标签位于其子项尚未显示的选项卡项上,因此第二个标签的 IsHandleCreated
是 false
。这意味着,第二个标签的 Handle
属性 不会在 BackgroundWorker
的线程上访问。相反,文本值只是缓存在控件内的 text
字段中。因此——也不例外。
当您激活第二个选项卡项时,将创建 Label
的句柄,并且 .NET Framework 代码从 text
字段中获取缓存的文本值并将其应用于标签.这发生在 UI 线程上,所以再次 - 那里没有例外。
您可以先切换到第二个选项卡项,然后然后按下您的按钮。您将观察到异常。这是因为在这种情况下已经创建了第二个标签的句柄。
作为一般注意事项 - 您应该 永远不会 从工作线程访问 UI 元素,无论您是否观察到这些异常。对于所有与来自其他线程的UI元素的交互,使用同步:Control.Invoke
或Control.BeginInvoke
、SynchronizationContext
、TaskScheduler
等
这不是重复的。 Whosebug 上尚未探索 WinForms TabControl 的这种特殊行为。
查看下面的示例:
我在 .NET 4.0 中有一个带有两个选项卡的 TabControl。每个选项卡中都有一个 Label。当我单击 Button 时,我启动了 BackgroundWorker,它现在在非 UI 线程上运行。如果我尝试从 tabPage1 更改 Label,由于跨线程调用,我会收到 InvalidOperationException。但是修改 tabPage2 上的 Label 的第二行运行得非常好——没有例外。
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += Bgw_DoWork;
bgw.RunWorkerAsync();
}
private void Bgw_DoWork(object sender, DoWorkEventArgs e)
{
try
{
label1.Text = "Testing tabPage1"; // This is sitting on tabPage1 - THROWS CROSS THREAD OPERATION
label2.Text = "Testing tabPage2"; // This is sitting on tabPage2 - RUNS FINE
}
catch (Exception ex)
{
throw ex;
}
}
为什么这在 tabPage2 上允许但在 tabPage1 上不允许。在这两种情况下,我们似乎都在从非 UI 线程修改 UI。
This isControl.Text
setter属性:
set
{
// some code omitted
this.WindowText = value;
this.OnTextChanged(EventArgs.Empty);
// some code omitted
}
它只是转发到 Control.WindowText
属性。让我们检查一下:
set
{
if (value == null) value = "";
if (!WindowText.Equals(value)) {
if (IsHandleCreated) {
UnsafeNativeMethods.SetWindowText(new HandleRef(window, Handle), value);
}
else {
if (value.Length == 0) {
text = null;
}
else {
text = value;
}
}
}
}
InvalidOperationException
来源于Handle
属性getter,调用获取HandleRef
实例,用于native方法调用
get {
if (checkForIllegalCrossThreadCalls &&
!inCrossThreadSafeCall &&
InvokeRequired) {
throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall, Name));
}
// further code omitted
}
Handle
属性 只有在 IsHandleCreated
为 true
时才会被访问。在您的情况下,第二个标签位于其子项尚未显示的选项卡项上,因此第二个标签的 IsHandleCreated
是 false
。这意味着,第二个标签的 Handle
属性 不会在 BackgroundWorker
的线程上访问。相反,文本值只是缓存在控件内的 text
字段中。因此——也不例外。
当您激活第二个选项卡项时,将创建 Label
的句柄,并且 .NET Framework 代码从 text
字段中获取缓存的文本值并将其应用于标签.这发生在 UI 线程上,所以再次 - 那里没有例外。
您可以先切换到第二个选项卡项,然后然后按下您的按钮。您将观察到异常。这是因为在这种情况下已经创建了第二个标签的句柄。
作为一般注意事项 - 您应该 永远不会 从工作线程访问 UI 元素,无论您是否观察到这些异常。对于所有与来自其他线程的UI元素的交互,使用同步:Control.Invoke
或Control.BeginInvoke
、SynchronizationContext
、TaskScheduler
等