在控制台应用程序中,为什么同步阻塞代码 (Thread.Sleep(..)) 在等待的异步任务中使用时表现得像多线程? [复制]

2024-01-03

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}\n 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

我期待的是 - 在第 65 行(在代码中),Thread.Sleep(10000);- 每个任务应该独立阻塞 10 秒(因为我已经使用Thread.Sleep(10000)这是同步和阻塞代码)。

然而从上面的输出来看,它看起来像Thread.Sleep(阻塞操作)就像多线程一样发生。

我明白 - async+await 没有Task.Run()对于 Windows 应用程序,使用相同的线程(UI 线程)。然后Thread.Sleep是一个同步阻塞操作。

  1. 那么为什么每个Task不阻塞10秒?以及如何使每个Task封锁10秒?

  2. 我被告知此行为与同步上下文有关。因此它从线程池中提取线程(与 Windows 窗体应用程序的情况不同)。那么我想问的是——这是否意味着它将执行多线程(多个线程并行运行)?


await Task.Delay(5000);在模拟 IO 密集型操作方面做得非常好。尽管 IO 绑定操作不使用线程来等待完成(请参阅没有线程 https://blog.stephencleary.com/2013/11/there-is-no-thread.html斯蒂芬·克利里 (Stephen Cleary) 的文章,也docs https://learn.microsoft.com/en-us/dotnet/standard/async-in-depth可以阐明一些),延续将在线程池上运行。所以downloadTasksQuery.ToList()将开始你所有的await Task.Delay然后是并行的(取决于任务和线程池的数量以及SynchronizationContext)其中部分或全部设置可以在单独的线程上继续。

那么为什么每个Task都没有阻塞10秒呢?那么如何让每个任务阻塞 10 秒呢?

它会阻塞,但在您的情况下它会阻塞一个单独的线程。

我被告知此行为与同步上下文有关。

是的,此行为可能会受到同步上下文的影响。例如,在桌面应用程序中,未标记的延续ConfigureAwait(false)将在单个 UI 线程上运行,因为您没有ConfigureAwait(false)配置为await Task.Delay(5000)实际上,您最终会导致 UI 无响应。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在控制台应用程序中,为什么同步阻塞代码 (Thread.Sleep(..)) 在等待的异步任务中使用时表现得像多线程? [复制] 的相关文章

随机推荐