异步运行的任务,即使 Main 方法未声明为异步

Tasks running asynchronously even though Main method is not declared as async

提问人:Armaho 提问时间:8/29/2023 最后编辑:Theodor ZouliasArmaho 更新时间:8/29/2023 访问量:80

问:

我写了下面的代码,忘记将 Main 方法声明为异步,所以我的猜测是 and 任务应该同步运行。producerconsumer

using System.Collections.Concurrent;

namespace MainProgram;

public class Program
{
    public static void Main(string[] args)
    {
        using var intCollection = new BlockingCollection<int>();
        int sum = 0;

        Task.Run(() =>
        {
            Console.WriteLine("Running Producer");
            Thread.Sleep(1000);
            for (int i = 0; i < 10; i++)
            {
                intCollection.Add(i);

                Console.WriteLine(i);
            }

            intCollection.CompleteAdding();
        });

        Task.Run(() =>
        {
            Console.WriteLine("Running Consumer");
            Thread.Sleep(1000);
            while (!intCollection.IsCompleted)
            {
                sum += intCollection.Take();
                Console.WriteLine("Thingi Thingi");
            }
        });

        Console.WriteLine("aaabbb");
        Console.ReadLine();
    }
}

但此代码的输出如下所示:

aaabbb
Running Producer
Running Consumer
0
Thingi Thingi
1
Thingi Thingi
2
Thingi Thingi
3
Thingi Thingi
4
Thingi Thingi
5
Thingi Thingi
6
Thingi Thingi
7
8
9
Thingi Thingi
Thingi Thingi
Thingi Thingi

这让我感到惊讶,因为这些任务似乎是异步运行的。有人可以向我解释一下吗?

C# 异步 async-await 并发 任务

评论

7赞 jmcilhinney 8/29/2023
你应该对实际作用做一些研究。从字面上看,它是从同步方法异步运行任务的方法。如果未声明方法,则无法使用 调用异步方法。在这种情况下,您只能同步调用方法。Task.Runawaitasync
3赞 Jonathan Willcock 8/29/2023
这是预期行为。启动第一个任务,然后继续执行并启动第二个任务。您没有等待任何一个结果,因此无论如何都会继续执行。然后,随着每个任务的进行,您会收到连续的消息
0赞 Roe 8/29/2023
Task.Run 仍然会异步运行里面的代码。此答案可能会帮助您以更好的方式解决问题
3赞 Olivier Jacot-Descombes 8/29/2023
async不会使事情异步运行(它纯粹是语法)。它只允许您使用 .await
1赞 Theodor Zoulias 8/29/2023
顺便说一句,消费者循环中存在竞争条件。使用 a 的正确方法是 GetConsumingEnumerable 方法。这不是唯一正确的方法,但却是最简单的方法。while (!intCollection.IsCompleted)BlockingCollection<T>

答:

0赞 Theodor Zoulias 8/29/2023 #1

Task.Run 方法调用 ThreadPool 上的委托。因此,在调用两次此方法后,生成了 2 个线程来处理工作请求。此时,您的程序至少有 3 个线程处于活动状态:进程的主线程和 .看到两个后台线程同时运行是预期行为。方法是否是无关紧要的。actionThreadPoolThreadPoolMainasync

如果您调用了 100 次,则不会立即生成 100 个线程来满足需求。您可以在此处此处找到创建和销毁线程的算法的描述。它不是很复杂。它涉及一个阈值,该阈值等于计算机中的 CPU 内核数,并且可通过 SetMinThreads API 进行调整。Task.RunThreadPoolThreadPool