user20584
user20584

Reputation: 41

Why does a Thread.Sleep running in a Task not block the WinForms UI Thread?

I'm currently playing around with Tasks in C# and Window Forms and I've ran into a strange effect. I have a Form which contains a timer that ticks every 300 ms. The tick event changes the background of a control in that form to a random color. I have another button, which when clicked starts a new Task which simply uses Thread.Sleep to wait 3 seconds. I also inserted a text box for logging.

Since from what I understand about Tasks they don't create new threads to run the tasks on (and the log also shows this), I expect the first button to stop changing it's color for 3 seconds while the task is running, since a thread can only do one thing at once. Either the button flashes or it does nothing for 3 seconds.

However that assumption appears to be wrong, since the button will happily change its color even while the thread is supposedly sleeping! How can this be?

Follow up: I noticed that from the tasks method, I have to use Invoke to access the logging textbox. However, according to MSDN's Control.InvokeRequired documentation:

true if the control's Handle was created on a different thread than the calling thread (indicating that you must make calls to the control through an invoke method); otherwise, false.

Since this is a single-threaded scenario how can InvokeRequired be true?

P.S.: I know that Task.Delay is a thing. I want to understand why the UI thread doesn't block during a Thread.Sleep.

Log output::

[T9] Before await
[T9] [I] Task Start
[T9] [I] Task End
[T9] After await

The flashing button also shows the thread ID of the thread in which the tick event handler executes, and it's also 9.

Full Code:

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

Upvotes: 3

Views: 1171

Answers (1)

Eric Lippert
Eric Lippert

Reputation: 660189

Since from what I understand about Tasks they don't create new threads to run the tasks on

The Task doesn't necessarily create a new thread itself, but Task.Factory.StartNew does. It schedules the task to execute using the current task scheduler, which by default fetches a worker thread from the thread pool.

Please read:

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

Upvotes: 7

Related Questions