为什么在任务中运行的Thread.Sleep不会阻止WinForms UI线程

本文关键字:WinForms 线程 UI Sleep 任务 运行 Thread 为什么 | 更新日期: 2023-09-27 18:19:34

我目前在C#和Window窗体中玩任务,遇到了一种奇怪的效果。我有一个窗体,它包含一个每300毫秒一次的计时器。tick事件将该窗体中控件的背景更改为随机颜色。我有另一个按钮,当点击它时,它会启动一个新的任务,只需使用Thread.Sleep等待3秒。我还插入了一个用于日志记录的文本框。

根据我对任务的理解,他们不会创建新的线程来运行任务(日志也显示了这一点),我希望第一个按钮在任务运行时停止更改颜色3秒钟,因为线程一次只能做一件事。按钮要么闪烁,要么在3秒钟内不起作用。

然而,这种假设似乎是错误的,因为即使线程处于睡眠状态,按钮也会很高兴地改变颜色!这怎么可能?

后续:我注意到,从tasks方法中,我必须使用Invoke来访问日志文本框。但是,根据MSDN的Control.InvokeRequired文档:

如果控件的Handle是在与调用线程不同的线程上创建的(表示必须通过invoke方法调用控件),则为true;否则为false。

既然这是一个单线程场景,InvokeRequired怎么可能是真的?

附言:我知道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");
        }
    }
}

为什么在任务中运行的Thread.Sleep不会阻止WinForms UI线程

根据我对任务的理解,他们不会创建新的线程来运行上的任务

任务不一定会创建一个新线程本身,但Task.Factory.StartNew会创建。它使用当前任务调度程序来调度要执行的任务,默认情况下,该调度程序从线程池中获取一个工作线程。

请阅读:

http://msdn.microsoft.com/en-us/library/dd997402(v=vs.110).aspx