【发布时间】:2014-10-20 05:47:59
【问题描述】:
我目前正在使用 C# 和 Window Forms 中的任务,我遇到了一个奇怪的效果。
我有一个表格,其中包含一个每 300 毫秒计时一次的计时器。滴答事件将该表单中的控件的背景更改为随机颜色。我有另一个按钮,单击它会启动一个新任务,它只使用Thread.Sleep 等待 3 秒。我还插入了一个用于记录的文本框。
根据我对任务的了解,他们不会创建新线程来运行任务(日志也显示了这一点),我希望第一个按钮在任务运行时停止更改颜色 3 秒,因为一个线程一次只能做一件事。要么按钮闪烁,要么在 3 秒内什么都不做。
然而,这个假设似乎是错误的,因为即使线程应该处于休眠状态,按钮也会很高兴地改变它的颜色!这怎么可能?
跟进:我注意到从任务方法中,我必须使用Invoke 来访问日志文本框。但是,根据 MSDN 的Control.InvokeRequired 文档:
如果控件的句柄是在与调用线程不同的线程上创建的,则为真(表明您必须通过调用方法调用控件);否则为假。
既然这是一个单线程场景,InvokeRequired 怎么可能是真的?
P.S.:我知道Task.Delay 是一个东西。我想了解为什么 UI 线程在 Thread.Sleep 期间不会阻塞。
日志输出::
[T9] Before await
[T9] [I] Task Start
[T9] [I] Task End
[T9] After await
闪烁的按钮还显示了tick事件处理程序执行的线程的线程ID,也是9。
完整代码:
using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncAwaitTest
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public class Form1 : Form
{
private readonly Button _buttonFlash;
private readonly System.Windows.Forms.Timer _timerFlash;
private readonly TextBox _textLog;
private readonly Random _rand = new Random();
public Form1()
{
_buttonFlash = new Button();
var buttonAwait = new Button();
_timerFlash = new System.Windows.Forms.Timer();
_textLog = new TextBox();
SuspendLayout();
_buttonFlash.Location = new Point(12, 12);
_buttonFlash.Size = new Size(139, 61);
buttonAwait.Location = new Point(213, 12);
buttonAwait.Size = new Size(110, 61);
buttonAwait.Text = "Wait Some Time";
buttonAwait.Click += buttonAwait_Click;
_timerFlash.Interval = 300;
_timerFlash.Tick += TimerFlashTick;
_textLog.Location = new Point(36, 79);
_textLog.Multiline = true;
_textLog.Size = new Size(351, 167);
ClientSize = new Size(480, 286);
Controls.Add(_textLog);
Controls.Add(buttonAwait);
Controls.Add(_buttonFlash);
Text = "Form1";
ResumeLayout(false);
PerformLayout();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_timerFlash.Start();
}
private void Log(string text)
{
if (InvokeRequired)
{
Invoke((Action<string>) Log, "[I] " + text);
return;
}
_textLog.Text += string.Format("[T{0}] {1}{2}", Thread.CurrentThread.ManagedThreadId, text, Environment.NewLine);
}
private void TimerFlashTick(object sender, EventArgs e)
{
_buttonFlash.Text = Thread.CurrentThread.ManagedThreadId.ToString();
_buttonFlash.BackColor = Color.FromArgb(255, _rand.Next(0, 255), _rand.Next(0, 255), _rand.Next(0, 255));
}
private async void buttonAwait_Click(object sender, EventArgs e)
{
Log("Before await");
await Task.Factory.StartNew(Something);
Log("After await");
}
private void Something()
{
Log("Task Start");
Thread.Sleep(3000);
Log("Task End");
}
}
}
【问题讨论】:
-
请出示您的日志。我希望
Something在非 UI 线程中执行,这与“因为根据我对任务的理解,他们不会创建新线程来运行任务(日志也显示了这一点)”。请注意,仅当您记录线程 ID 时,您一定会在 UI 线程中... -
完成。我唯一能想象到的问题是
Thread.CurrentThread.ManagedThreadId在骗我。 -
啊啊啊啊,我只是在调用UI线程后才将线程ID添加到日志消息中!这就解释了一切。
-
要发现的是,您已经在其中找到了
[I]...
标签: c# .net winforms asynchronous task