Запустит ли Task.Delay новый поток?

Следующий код должен (по крайней мере, на мой взгляд) создать 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 мс)
  • В консоли моно показано множество созданных тем:

Загруженная сборка: /Users/xxxxx/Programming/xxxxx/xxxxxxxxxx/bin/Release/xxxxx.exe

Тема начата: # 2

Тема начата: # 3

Тема начата: # 4

Тема начата: # 5

Тема начата: # 6

Тема начата: # 7

Тема закончена: # 3 <- Очевидно, задержка в 1000 мс закончена?

Тема закончена: # 2 <- Очевидно, задержка в 1000 мс закончена?

Тема начата: # 8

Тема начата: # 9

Тема начата: # 10

Тема начата: # 11

Тема начата: # 12

Тема начата: # 13

... ты понял.

Это на самом деле ошибка? Или я неправильно использую библиотеку?

[РЕДАКТИРОВАТЬ] Я проверил пользовательский метод сна с использованием таймера:

    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] Просто к сведению: я протестировал исходный код (с помощью Task.Delay) в .NET, используя Windows 8.1, и теперь он работает как ожидалось (1000 Tasks, ожидание в течение 1 секунды параллельно и завершение).

Таким образом, ответ: Mono Impl. из (некоторых) методов не идеален. В общем случае Task.Delay не запускает поток, и даже многие из них не должны создавать несколько потоков.

Ответ 1

Библиотека Task больше предназначена для управления задачами блокировки без блокировки всего рабочего процесса (задача асинхронизма, путано называемая "параллельная задача" Microsoft) и не для выполнения больших блоков параллельных вычислений (параллельное выполнение).

В библиотеке задач используются планировщики и очереди, готовые к выполнению. Когда задания выполняются, они будут делать это в потоке потока потока, и они очень ограничены по количеству. Существует логика для увеличения количества потоков, но если у вас нет сотен ядер процессора, он будет оставаться низким.

Итак, чтобы ответить на вопрос, некоторые из ваших задач поставлены в очередь, ожидая потока из пула, а другие задачи с задержкой были выпущены планировщиком.

Планировщик и логика пула потоков могут быть изменены во время выполнения, но если вы пытаетесь быстро выполнить множество вычислений, Task не подходит для задания. Если вы хотите иметь дело с множеством медленных ресурсов (например, на диске, базе данных или интернет-ресурсах), Task может помочь поддерживать отзыв приложения.

Если вы просто хотите узнать о Task, попробуйте следующее:

Ответ 2

На рабочем столе .NET Framework.

Короче говоря, существует специальный поток VM, который периодически проверяет очередь таймеров и запускает делегаты таймеров в очереди пула потоков. Task.Delay не создает новый поток, но все же может быть тяжелым, и нет никаких гарантий в отношении порядка выполнения или точных сроков. И, как я понимаю, прохождение отмены Task.Delay может в итоге просто удалить элемент из коллекции, без работы пула потоков в очереди.

Task.Delay запланирован как DelayPromise путем создания нового System.Threading.Timer. Все таймеры хранятся в синглтоне AppDomain TimerQueue. Встроенный таймер виртуальной машины используется для обратного вызова .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)