【问题标题】:Does Task.Delay start a new thread?Task.Delay 是否启动一个新线程?
【发布时间】:2014-03-19 15:21:03
【问题描述】:

以下代码应该(至少在我看来)创建 100 个Tasks,它们都在并行等待(这就是并发的重点,对吧 :D ?)并且几乎同时完成。我猜每个Task.Delay 都会在内部创建一个Timerobject。

public static async Task MainAsync() {

    var tasks = new List<Task>();
    for (var i = 0; i < 100; i++) {
        Func<Task> func = async () => {
            await Task.Delay(1000);
            Console.WriteLine("Instant");
        };
        tasks.Add(func());
    }
    await Task.WhenAll(tasks);
}

public static void Main(string[] args) {
    MainAsync().Wait();
}

但是!当我在 Mono 上运行它时,我得到了非常奇怪的行为:

  • Tasks 不会同时完成,有很大的延迟(可能大约 500-600 毫秒)
  • 在控制台单声道中显示了很多创建的线程:

加载的程序集:/Users/xxxxx/Programming/xxxxx/xxxxxxxxxx/bin/Release/xxxxx.exe

线程开始:#2

线程开始:#3

线程开始:#4

线程开始:#5

线程开始:#6

线程开始:#7

Thread finished: #3

Thread finished: #2

线程开始:#8

线程开始:#9

线程开始:#10

线程开始:#11

线程开始:#12

线程开始:#13

...你明白了。

这实际上是一个错误吗?还是我用错了图书馆?

[编辑] 我使用 Timer 测试了自定义睡眠方法:

    public static async Task MainAsync() {
        Console.WriteLine("Started");
        var tasks = new List<Task>();
        for (var i = 0; i < 100; i++) {
            Func<Task> func = async () => {
                await SleepFast(1000);
                Console.WriteLine("Instant");
            };
            tasks.Add(func());
        }
        await Task.WhenAll(tasks);
        Console.WriteLine("Ready");
    }

    public static Task SleepFast(int amount) {
        var source = new TaskCompletionSource<object>();
        new Timer(state => {
            var oldSrc = (TaskCompletionSource<object>)state;
            oldSrc.SetResult(null);
        }, source, amount, 0);
        return source.Task;
    }

这一次,所有任务瞬间完成。所以,我认为这是一个非常糟糕的实现或错误。

[编辑2] 仅供参考:我现在使用 Windows 8.1 在 .NET 上测试了原始代码(使用 Task.Delay),它按预期运行(1000 Tasks,并行等待 1 秒并完成)。

所以答案是:Mono 的 impl。 (某些)方法并不完美。通常Task.Delay 不会启动线程,甚至其中很多也不应该创建多个线程。

【问题讨论】:

  • Task.Delay 在内部使用一个计时器,该计时器在内部使用线程池。由于您目前正在非常快速地创建 100 个任务,我猜您的线程池中的线程即将用完,因此会自动创建新线程。
  • 那么计时器不是使用可扩展技术实现的吗?我希望有一些非常有效的东西。例如,在 node.js 中,我相信他们使用 epoll(用于 linux)。
  • 每个任务间隔 500-600 毫秒?你能发布确切的时间吗?
  • Task.Delay 操作本身不会创建新线程,但延迟结束后执行Console.WriteLine 的回调方法肯定需要在线程上执行。如果所有延迟都相同,则所有这些回调将大致同时发生。尝试随机化示例中的延迟,看看它是否使用更少的线程。
  • 您是否将此作为错误报告给 Mono 项目?我强烈反对糟糕的概念。 “可能创建线程”是一个错误的措辞,总是有一个 I/O 完成线程,它在硬件中断后运行代码。在使用 node.js 的情况下,它只会将完成回调编组到节点的主线程,在那里它会被事件循环拾取。您可以使用 .NET 使用自定义任务调度程序轻松实现此模型:stackoverflow.com/q/20993007/1768303

标签: c# multithreading mono .net-4.5 async-await


【解决方案1】:

Task 库更多地用于管理阻塞任务而不阻塞整个工作流(任务异步,被微软混淆称为“任务并行”),用于执行大块并发计算(并行执行)。

任务库使用调度程序并将准备执行的作业排队。当作业运行时,它们将在线程池线程上执行,并且这些线程的数量非常有限。增加线程数是有逻辑的,但除非你有数百个 CPU 内核,否则它会保持在一个低水平。

所以要回答这个问题,您的一些任务排队等待池中的线程,而其他延迟任务已由调度程序发出。

调度程序和线程池逻辑可以在运行时更改,但如果您想快速完成大量计算,Task 不适合这项工作。如果您想处理大量慢速资源(如磁盘、数据库或 Internet 资源)Task 可能有助于保持应用响应。

如果你只是想了解Task,试试这些:

【讨论】:

  • 那么使用任务库编写高性能服务器是不可能的? (因为在服务器场景中,您肯定有很多小/快任务。)
  • 更像你可以使用任务库来确保你的服务器没有被 IO 阻塞。任务不会解决性能问题,但它是一个有用的工具。它们更像是“绿色线程”或 Erlang 线程——它们不会使您的硬件运行得更快,但它们可以帮助更好地利用可用的电源。与优化一样,先测量
【解决方案2】:

在 .NET Framework 桌面上。

简而言之,这个特殊的 VM 线程会定期检查计时器队列并在线程池队列上运行计时器的委托。 Task.Delay 不会创建新线程,但仍然可能很重,并且不能保证执行顺序或截止日期的精确性。据我了解,通过取消Task.Delay 可能最终只是从集合中删除项目,没有线程池工作排队。

Task.Delay 通过创建新的System.Threading.Timer 计划为DelayPromise。所有计时器都存储在TimerQueue 的AppDomain 单例中。 Native VM timer 用于回调 .NET 以检查是否需要从队列中触发任何计时器。通过ThreadPool.UnsafeQueueUserWorkItem 安排执行的计时器委托。

从性能的角度来看,如果延迟提前结束,取消延迟似乎更好:

open System.Threading
open System.Threading.Tasks

// takes 0.8% CPU
while true do
  Thread.Sleep(10)
  Task.Delay(50)

// takes 0.4% CPU
let mutable a = new CancellationTokenSource()
while true do
  Thread.Sleep(10)
  a.Cancel()
  a.Dispose()
  a <- new CancellationTokenSource()
  let token = a.Token
  Task.Delay(50,token)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-28
    • 1970-01-01
    • 2019-04-08
    • 2011-07-20
    • 2012-05-06
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    相关资源
    最近更新 更多