c# - 在新的Thread()中等待是否毫无意义?

标签 c# multithreading asynchronous async-await

样例代码:

    class Program
    {
        static void sleepFunc()
        {
            int before = Thread.CurrentThread.ManagedThreadId;
            Thread.Sleep(5000);
            int after = Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine($"{before} -> sleep -> {after}");
        }

        static async void delayFunc()
        {
            int before = Thread.CurrentThread.ManagedThreadId;
            await Task.Delay(5000);
            int after = Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine($"{before} -> delay -> {after}");
        }
        static void Main(string[] args)
        {
            List<Thread> threads = new List<Thread>();
            for(int i = 0; i < 10; i++)
            {
                var thread = new Thread(sleepFunc);
                thread.Start();
                threads.Add(thread);
            }
            Thread.Sleep(1000); // just to separate the result sections
            for (int i = 0; i < 10; i++)
            {
                var thread = new Thread(delayFunc);
                thread.Start();
                threads.Add(thread);
            }
            Console.ReadLine();
        }
    }
样本输出:
3 -> sleep -> 3
7 -> sleep -> 7
4 -> sleep -> 4
5 -> sleep -> 5
6 -> sleep -> 6
8 -> sleep -> 8
9 -> sleep -> 9
10 -> sleep -> 10
11 -> sleep -> 11
12 -> sleep -> 12
21 -> delay -> 25
18 -> delay -> 37
15 -> delay -> 36
16 -> delay -> 32
19 -> delay -> 24
20 -> delay -> 27
13 -> delay -> 32
17 -> delay -> 27
22 -> delay -> 25
14 -> delay -> 26
Thread.Sleep()的延续在同一显式创建的线程上运行,而Task.Delay()的延续在不同的(线程池)线程上运行。
由于延续总是在线程池上运行,因此如果函数内有任何等待,使用新的Thread(func)只是毫无意义/浪费/反模式吗?有什么方法可以强制任务在原始的非线程池新线程上继续执行?
我问是因为我读过有人建议在线程中而不是在Task中放置长时间运行的循环(例如,用于套接字通信),但是似乎如果他们调用ReadAsync,那么它最终将成为线程池上的Task无论如何,新的线程毫无意义吗?

最佳答案

Since the continuation always runs on the thread pool, is it just pointless/wasteful/antipattern to use a new Thread(func) if there is an await anywhere inside the func?


永不说永不。但是总的来说,是的,这是没有意义的。

Is there any way to force a task to continue on the original non-thread-pool new thread?


是的,有办法。创建您自己的同步上下文并在该线程中运行该上下文。然后,任何await都会在该上下文中继续(当然,默认情况下……如果您调用ConfigureAwait(false),则不允许这样做)。
创建自己的同步上下文相对很少(应该)。您是在自然有其自身的上下文中运行代码,还是在您实际上不在乎哪个线程处理代码的情况下。
显式线程和async/await并不是很好地结合在一起。 await语句是一种以线性,同步同步方式组成异步代码的方法。通常,如果创建了显式线程,那是因为您打算在该线程中执行所有工作。在这种情况下甚至没有理由调用异步方法。
相反,如果您使用的是异步方法,那么根据定义,这些方法将以与您使用的任何线程无关的方式异步执行。许多异步操作甚至根本不使用线程!如果您在逻辑中使用await,那么根据定义,您的代码会短暂运行一段时间,然后在等待异步完成的过程中被暂停。在这种情况下,没有理由使用显式线程。当您不等待其他时间时,线程池是在短暂的时间间隔内执行自己的代码的理想方法。

I'm asking because I read someone recommending to put a long running loop (for socket communication for example) in a Thread instead of in a Task, but it seems that if they call ReadAsync then it ends up being a Task on the thread pool anyway, and the new Thread was pointless?


I/O是使用await是一种理想技术的经典示例。套接字(从CPU的角度来看)需要很长的时间来等待某件事的发生,而被非常短的时间段打断,以便处理显示的任何数据。通常情况下,将整个线程专用于从套接字读取是个坏主意。在.NET之前很久,仍然存在一个异步I/O模型,该模型允许线程池处理这些间歇性完成的I/O操作。请参见“I/O完成端口”。实际上,.NET套接字API是在该API之上构建的。
如果一个连接很少,则可以摆脱“每个连接一个线程”的模型,但是扩展性很差。使用BSD套接字API中的“选择”模型的较旧替代方案效果更好,但效率很低。使用IOCP,Windows可以有效地管理处理大量套接字的几个线程,而不必将太多的CPU时间专用于I/O,也不必占用太多的线程(每个线程在Windows上使用相对大量的资源)。
显式线程不仅在您描述的场景中毫无意义,而且还有其他非常好的理由无论如何都不这样做。

有用的附加阅读:
What is the difference between task and thread?
Task vs Thread differences

关于c# - 在新的Thread()中等待是否毫无意义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64603106/

相关文章:

c# - Entity Framework 和 DBContext 问题

java - 在 Canvas 上绘制路径作为动画

javascript - 通过 Ajax 发布到 MySql 的问题

c# - 从其他属性的代码中测试属性

c# - 模式验证 XML

java - 在 catch 中启动一个新线程

c++ - 在多线程环境中交换 c++ 映射对象

c# - 确保调用函数两次时返回 C# 异步任务的最佳方法是什么?

c# - 如何保证延续按任务完成顺序运行?

重复模式的 C# 正则表达式