xperious Asked:2020-05-07 09:59:29 +0000 UTC2020-05-07 09:59:29 +0000 UTC 2020-05-07 09:59:29 +0000 UTC 任务和本机线程 772 你好,我不明白从操作系统的角度来看什么是任务......他们到处都写到基于任务的并行性从池中获取“任务”......但是“任务”是什么意思?这是一个有自己的堆栈的本机线程......我是否正确理解任务是“没有自己的堆栈的线程”,即 光流?如果是这样,它是如何在操作系统方面实现的?我假设 async/await 以某种方式使用轻量级线程的概念是否正确? c# 2 个回答 Voted Best Answer VladD 2020-05-07T19:03:57Z2020-05-07T19:03:57Z 不,你不太明白。 对于初学者来说,正如@PetSerAl 正确指出的那样,它Task是 .NET 的内部概念,不以任何方式映射到操作系统对象。 那么,“基于任务的并行性从池中获取一个“任务””的信息是完全错误的。最容易将其Task视为一个承诺(它确实是)。 您应该只将它视为Task在某个时间点结束的对象。“完成”一词仅表示它进入状态Completed,并且调用var result = await task;也随之终止。这await不是阻塞调用,它会停止当前代码的执行,并在 promise 结束时恢复执行。 事实上,它是如何实现Task的,在什么情况下它实际上是运行在某个线程(或任何线程)中的代码,以及它根本不运行的代码(例如订阅了某个事件的代码,并完成了任务当此事件到达时)。您应该只对完成并产生结果(或异常)的承诺的语义感兴趣。 它看起来像一个轻量级线程,但它不是:线程不产生结果,而只是简单地运行,不像任务,它以某种方式在某个遥远的时间点接收结果。但是在这两种情况下,都没有专门的栈来为它服务:毕竟线程和栈是一个更底层的概念。 Task一个不占用任何线程的 'a的例子很容易构造: Task Delay(int milliseconds) { var tcs = new TaskCompletionSource<object>(); var timer = new System.Threading.Timer( _ => { tcs.SetCompleted(null); timer.Dispose(); }, null, milliseconds, System.Threading.Timeout.Infinite); return tcs.Task; } 这个是类似的Task.Delay。 有关该主题的更多信息: 没有线程(原文:没有线程)。 等待异步如何工作 您是否需要异步/等待? Alexander Petrov 2020-05-07T21:16:18Z2020-05-07T21:16:18Z Vlad 在他的回答中强调了任务的高级概念,而据我了解,topikstarter 从内部对设备更感兴趣。我将描述我的看法。 编程中的所有任务都可以有条件地分为两种类型: CPU 绑定 - CPU 负载、计算任务; IO-bound - I/O 操作,向/从文件/网络发送/接收数据等。 对于第二个任务,不需要单独的线程。它们在特殊的输入输出完成端口——IOCP(IO 完成端口)上执行。粗略地说,处理器给控制器一个命令:从某某地址复制一段内存到这样的流,之后它就可以继续处理它的业务了。当控制器完成它的工作时,它向CPU发送一个硬件中断(IRQ)(相当于头上的一个屁股)任务已经完成。CPU 以一种或另一种方式对此做出反应。 对于繁重的计算,需要一个线程。创建一个线程是一个相当昂贵的操作,所以系统维护一个运行线程池(它的大小可以改变)。你可以创建一个新线程,你可以从池中取出它。 当通过调用or创建计算任务时,线程将从池中取出。您可以在方法中指定一个参数- 在这种情况下,将创建一个新线程,而不是从池中获取。事实是,长时间占用池中的线程是不可取的,因为其他应用程序可能随时需要它们。因此,习惯上对池中的线程执行相对较短的操作。Task.Factory.StartNewTask.RunStartNewTaskCreationOptions.LongRunning 应该注意的是,当在单独的线程中执行冗长的 IO 操作时,操作系统能够跟踪情况:它会自动将它们传输到 IOCP。当池中的一个线程(长时间占用是非常不受欢迎的!)在 I/O 上挂起时,池调度程序会自动向池中添加一个新线程(它可以检测到这一点)以防止所谓的饥饿系统的。 目前,compute Task直接对应于 managed flow Thread。但是,这可能会随时更改,因此不应依赖。反过来,托管线程直接对应于本机操作系统线程。同样,它可以随时更改,不能抵押。
不,你不太明白。
对于初学者来说,正如@PetSerAl 正确指出的那样,它
Task
是 .NET 的内部概念,不以任何方式映射到操作系统对象。那么,“基于任务的并行性从池中获取一个“任务””的信息是完全错误的。最容易将其
Task
视为一个承诺(它确实是)。您应该只将它视为
Task
在某个时间点结束的对象。“完成”一词仅表示它进入状态Completed
,并且调用var result = await task;
也随之终止。这await
不是阻塞调用,它会停止当前代码的执行,并在 promise 结束时恢复执行。事实上,它是如何实现
Task
的,在什么情况下它实际上是运行在某个线程(或任何线程)中的代码,以及它根本不运行的代码(例如订阅了某个事件的代码,并完成了任务当此事件到达时)。您应该只对完成并产生结果(或异常)的承诺的语义感兴趣。它看起来像一个轻量级线程,但它不是:线程不产生结果,而只是简单地运行,不像任务,它以某种方式在某个遥远的时间点接收结果。但是在这两种情况下,都没有专门的栈来为它服务:毕竟线程和栈是一个更底层的概念。
Task
一个不占用任何线程的 'a的例子很容易构造:这个是类似的
Task.Delay
。有关该主题的更多信息:
Vlad 在他的回答中强调了任务的高级概念,而据我了解,topikstarter 从内部对设备更感兴趣。我将描述我的看法。
编程中的所有任务都可以有条件地分为两种类型:
对于第二个任务,不需要单独的线程。它们在特殊的输入输出完成端口——IOCP(IO 完成端口)上执行。粗略地说,处理器给控制器一个命令:从某某地址复制一段内存到这样的流,之后它就可以继续处理它的业务了。当控制器完成它的工作时,它向CPU发送一个硬件中断(IRQ)(相当于头上的一个屁股)任务已经完成。CPU 以一种或另一种方式对此做出反应。
对于繁重的计算,需要一个线程。创建一个线程是一个相当昂贵的操作,所以系统维护一个运行线程池(它的大小可以改变)。你可以创建一个新线程,你可以从池中取出它。
当通过调用or创建计算任务时,线程将从池中取出。您可以在方法中指定一个参数- 在这种情况下,将创建一个新线程,而不是从池中获取。事实是,长时间占用池中的线程是不可取的,因为其他应用程序可能随时需要它们。因此,习惯上对池中的线程执行相对较短的操作。
Task.Factory.StartNew
Task.Run
StartNew
TaskCreationOptions.LongRunning
应该注意的是,当在单独的线程中执行冗长的 IO 操作时,操作系统能够跟踪情况:它会自动将它们传输到 IOCP。当池中的一个线程(长时间占用是非常不受欢迎的!)在 I/O 上挂起时,池调度程序会自动向池中添加一个新线程(它可以检测到这一点)以防止所谓的饥饿系统的。
目前,compute
Task
直接对应于 managed flowThread
。但是,这可能会随时更改,因此不应依赖。反过来,托管线程直接对应于本机操作系统线程。同样,它可以随时更改,不能抵押。