Jon*_*lis 6 c# multithreading task ui-thread
我刚回答了一个关于是否Task可以更新UI的问题.当我玩我的代码时,我意识到我不清楚自己的一些事情.
如果我有一个带有一个控件的窗体txtHello,我可以从任务更新UI,看起来,如果我立即执行它Task.Run:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
txtHello.Text = "Hello";
});
}
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我Thread.Sleep甚至5毫秒,CrossThread则抛出预期的错误:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
Thread.Sleep(5);
txtHello.Text = "Hello"; //kaboom
});
}
}
Run Code Online (Sandbox Code Playgroud)
我不确定为什么会这样.对于极短的运行,是否有某种优化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
Run Code Online (Sandbox Code Playgroud)
我们可以看到从Control.Handlegetter属性抛出异常.事实上,如果我们查看该属性的源代码,那就像预期的那样:
public IntPtr Handle {
get {
if (checkForIllegalCrossThreadCalls &&
!inCrossThreadSafeCall &&
InvokeRequired) {
throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall,
Name));
}
if (!IsHandleCreated)
{
CreateHandle();
}
return HandleInternal;
}
}
Run Code Online (Sandbox Code Playgroud)
有趣的是我们查看调用的代码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;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,Handle如果属性仅调用IsHandleCreated是true.
为了完整起见,如果我们查看IsHandleCreated的代码,我们会看到以下内容:
public bool IsHandleCreated {
get { return window.Handle != IntPtr.Zero; }
}
Run Code Online (Sandbox Code Playgroud)
所以,你没有得到异常的原因是因为在Task执行时,窗口句柄还没有被创建,这是Task从表单构造函数中的启动开始,也就是说,在表单之前甚至显示.
在创建窗口句柄之前,修改属性不需要UI线程中的任何工作.因此,在程序开始的这个小时间窗口中,似乎可以从非UI线程调用控件实例上的方法而不会出现"交叉线程"异常.但显然,这个特殊的小时间窗口的存在并没有改变这样一个事实,即我们应该始终确保从UI线程调用控制方法是安全的.
要证明窗口句柄创建的时间是获取(或不)"交叉线程"异常的决定因素,请尝试修改示例以在启动任务之前强制创建窗口句柄,并注意如何即使没有睡眠,您现在也会始终获得预期的异常:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// Force creation of window handle
var dummy = txtHello.Handle;
Task.Run(() =>
{
txtHello.Text = "Hello"; // kaboom
});
}
}
Run Code Online (Sandbox Code Playgroud)
相关文档:Control.Handle
如果尚未创建句柄,则引用此属性将强制创建句柄.
| 归档时间: |
|
| 查看次数: |
227 次 |
| 最近记录: |