You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

控制台应用中,被await的异步任务内的同步阻塞代码Thread.Sleep为何表现出多线程特性?

Understanding Thread.Sleep Behavior in Console Async Tasks

Sample Code

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace SampleAsyncConsoleProgram
{
    class Program
    {
        public static void ConsolePrint(string line)
        {
            Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " [" + Thread.CurrentThread.ManagedThreadId.ToString() + "] > " + line);
        }

        static readonly IEnumerable<string> s_urlList = new string[] { "website1", "website2", "website3", "website4" };

        static Task Main() => DownloadWebsites();

        static async Task DownloadWebsites()
        {
            ConsolePrint("Main program: Program started..");
            var stopwatch = Stopwatch.StartNew();
            ConsolePrint("Main program: Adding tasks to list..");

            IEnumerable<Task> downloadTasksQuery = from url in s_urlList select ProcessUrlAsync(url);
            List<Task> downloadTasks = downloadTasksQuery.ToList();

            ConsolePrint("Main program: Added tasks to list..");

            while (downloadTasks.Any())
            {
                ConsolePrint("Main program: Checking if a task is completed.... followed by await...");
                Task finishedTask = await Task.WhenAny(downloadTasks);
                ConsolePrint("Main program: A task was completed..");
                downloadTasks.Remove(finishedTask);
                await finishedTask;
            }

            stopwatch.Stop();
            ConsolePrint($"Main program: Program Completed Elapsed time: {stopwatch.Elapsed} Current time is " + DateTime.Now);
        }

        static async Task ProcessUrlAsync(string url)
        {
            ConsolePrint("Task: Starts downloading " + url);
            await Task.Delay(5000); //represents async call to fetch url
            ConsolePrint("Task: Sleeping for 10 sec.." + url);
            Thread.Sleep(10000); //represents some long running blocking synchronous work and keeping thread busy...
            ConsolePrint("Task: Wake up.." + url);
            ConsolePrint("Task: Done Task.." + url);
        }
    }
}

Output

13:39:24.567 [1] > Main program: Program started..
13:39:36.255 [1] > Main program: Adding tasks to list..
13:39:37.177 [1] > Task: Starts downloading website1
13:39:43.241 [1] > Task: Starts downloading website2
13:39:43.242 [1] > Task: Starts downloading website3
13:39:43.242 [1] > Task: Starts downloading website4
13:39:43.243 [1] > Main program: Added tasks to list..
13:39:43.243 [1] > Main program: Checking if a task is completed.... followed by await...
13:39:48.811 [5] > Task: Sleeping for 10 sec..website1
13:39:48.810 [7] > Task: Sleeping for 10 sec..website2
13:39:48.810 [4] > Task: Sleeping for 10 sec..website4
13:39:48.810 [6] > Task: Sleeping for 10 sec..website3
13:39:58.823 [4] > Task: Wake up..website4
13:39:58.826 [5] > Task: Wake up..website1
13:39:58.823 [6] > Task: Wake up..website3
13:39:58.826 [7] > Task: Wake up..website2
13:39:58.828 [5] > Task: Done Task..website1
13:39:58.828 [6] > Task: Done Task..website3
13:39:58.829 [7] > Task: Done Task..website2
13:39:58.828 [4] > Task: Done Task..website4
13:39:58.922 [7] > Main program: A task was completed..
13:39:58.923 [7] > Main program: Checking if a task is completed.... followed by await...
13:39:58.923 [7] > Main program: A task was completed..
13:39:58.923 [7] > Main program: Checking if a task is completed.... followed by await...
13:39:58.924 [7] > Main program: A task was completed..
13:39:58.924 [7] > Main program: Checking if a task is completed.... followed by await...
13:39:58.924 [7] > Main program: A task was completed..
13:39:58.985 [7] > Main program: Program Completed Elapsed time: 00:00:22.6686293 Current time is 30/07/2021 13:39:58

Your Questions Answered

1. Why isn't each Task blocking for 10 seconds individually?

Actually, each is blocking for 10 seconds—they're just doing it in parallel instead of sequentially. Here's the breakdown:

Console apps don't use a single-threaded synchronization context (unlike UI frameworks like Windows Forms/WPF). When you await a task (like Task.Delay(5000)), once the await completes, the runtime grabs an available thread from the thread pool to resume the rest of the method (the Thread.Sleep(10000) part).

In your code:

  • You kick off all four ProcessUrlAsync tasks quickly on the main thread.
  • Each hits await Task.Delay(5000) and suspends, freeing the main thread to finish creating all tasks.
  • After 5 seconds, all delays finish, and the thread pool assigns four separate threads (you can see the distinct IDs: 5,7,4,6) to run each Thread.Sleep(10000) at the same time.

Instead of waiting 10 seconds per task one after another, all four sleep operations run in parallel—so total elapsed time is roughly 5s (delay) + 10s (parallel sleep) plus minor thread pool scheduling overhead (which explains your ~22s total time).

2. How can I make each Task block 10 seconds independently?

You already are! But if you meant you want them to block sequentially (total time ~5 + 10*4 = 45s), modify your DownloadWebsites method to await each task one by one instead of using Task.WhenAny:

static async Task DownloadWebsites()
{
    ConsolePrint("Main program: Program started..");
    var stopwatch = Stopwatch.StartNew();
    ConsolePrint("Main program: Processing websites sequentially..");

    foreach (var url in s_urlList)
    {
        await ProcessUrlAsync(url);
    }

    stopwatch.Stop();
    ConsolePrint($"Main program: Program Completed Elapsed time: {stopwatch.Elapsed} Current time is " + DateTime.Now);
}

If you want to mimic UI thread behavior (all post-await code runs on the same thread, making Thread.Sleep block that single thread sequentially), you'll need a single-threaded synchronization context. You can use libraries like Nito.AsyncEx to add this to your console app:

static Task Main() => AsyncContext.Run(() => DownloadWebsites());

With this setup, all post-await code runs on one thread, so each Thread.Sleep(10000) will block that thread sequentially.

3. Does this mean the program is executing multi-threaded parallel operations?

Absolutely! The Thread.Sleep(10000) calls are running on four separate thread pool threads simultaneously—this is classic multi-threaded parallelism. The key difference from UI apps is that console apps don't restrict post-await execution to a single thread, so the runtime can spread work across multiple threads to maximize parallelism.


内容的提问来源于stack exchange,提问作者variable

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:27:45