Task.Delay是否开始一个新线程?

时间:2014-02-19 10:41:49

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

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

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毫秒)
  • 在控制台中,mono显示了很多创建的线程:

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

线程开始:#2

线程开始:#3

线程开始:#4

线程开始:#5

线程开始:#6

线程开始:#7

线程完成:#3&lt; - 显然延迟1000ms完成了?

线程完成:#2&lt; - 显然延迟了1000ms?

线程开始:#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;
    }

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

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

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

2 个答案:

答案 0 :(得分:3)

Task库设计用于管理阻止任务而不会阻塞整个工作流(任务异步,混淆地称为Microsoft的“任务并行”),用于执行大块并发计算(并行执行)。

任务库使用调度程序并将作业排队等待执行。当作业运行时,它们将在线程池线程上执行,并且这些线程的数量非常有限。有扩展线程数的逻辑,但除非你有数百个CPU核心,否则它将保持较低的数量。

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

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

如果您只是想了解Task请尝试以下内容:

答案 1 :(得分:1)

在.NET Framework桌面上。

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

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