gpt4 book ai didi

c# - 后台任务有时能够更新 UI?

转载 作者:太空狗 更新时间:2023-10-29 21:33:11 26 4
gpt4 key购买 nike

我刚刚回答了一个关于 Task 是否可以更新 UI 的问题。当我玩我的代码时,我意识到我在一些事情上不清楚。

如果我有一个带有一个控件 txtHello 的 Windows 窗体,我似乎可以从任务更新 UI,如果我立即在 Task.Run 上执行它:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
txtHello.Text = "Hello";
});
}
}

但是,如果我 Thread.Sleep 即使 5 毫秒,也会抛出预期的 CrossThread 错误:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
Thread.Sleep(5);
txtHello.Text = "Hello"; //kaboom
});
}
}

我不确定为什么会这样。是否对运行时间极短的 Task 进行了某种优化?

最佳答案

您没有发布异常堆栈跟踪,但我希望它看起来像这样:

System.InvalidOperationException: Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.set_WindowText(String value)
at System.Windows.Forms.TextBoxBase.set_WindowText(String value)
at System.Windows.Forms.Control.set_Text(String value)
at System.Windows.Forms.TextBoxBase.set_Text(String value)
at System.Windows.Forms.TextBox.set_Text(String value)
at WindowsFormsApplicationcSharp2015.Form1.<.ctor>b__0_0() in D:\test\WindowsFormsApplicationcSharp2015\Form1.cs:line 27

我们可以看到异常是从Control.Handle getter 属性中抛出的。事实上,如果我们看一下 source code对于该属性,正如预期的那样:

public IntPtr Handle {
get {
if (checkForIllegalCrossThreadCalls &&
!inCrossThreadSafeCall &&
InvokeRequired) {
throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall,
Name));
}

if (!IsHandleCreated)
{
CreateHandle();
}

return HandleInternal;
}
}

有趣的部分是当我们查看调用 Control.Handle 的代码时。在这种情况下,这是 Control.WindowText setter 属性:

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;
}
}
}
}

请注意,只有当 IsHandleCreatedtrue 时,才会调用 Handle 属性。

为了完整起见,如果我们看一下 IsHandleCreated 的代码我们看到以下内容:

public bool IsHandleCreated {
get { return window.Handle != IntPtr.Zero; }
}

所以,你没有得到异常的原因是因为当 Task 执行时,窗口句柄还没有创建,这是自 >任务 在表单的构造函数中开始,也就是说,甚至在表单显示之前。

在创建窗口句柄之前,修改属性还不需要 UI 线程做任何工作。因此,在程序开始时的这个小时间窗口内,似乎可以从非 UI 线程调用控件实例上的方法,而不会出现“跨线程”异常。但很明显,这个特殊的小时间窗口的存在并没有改变这样一个事实,即我们应该始终确保从 UI 线程调用控制方法是安全的。

为了证明创建窗口句柄的时间是获得(或不获得)“跨线程”异常的决定因素这一点,请尝试修改您的示例以在开始任务之前强制创建窗口句柄,并注意你现在如何始终如一地获得预期的异常,即使没有 sleep :

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

// Force creation of window handle
var dummy = txtHello.Handle;

Task.Run(() =>
{
txtHello.Text = "Hello"; // kaboom
});
}
}

相关文档:Control.Handle

If the handle has not yet been created, referencing this property will force the handle to be created.

关于c# - 后台任务有时能够更新 UI?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31886711/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com