线程.睡眠与任务.延迟

本文关键字:任务 延迟 线程 | 更新日期: 2023-09-27 18:19:44

我知道Thread.Sleep阻塞了一个线程。

但是Task.Delay也会阻止吗?还是就像Timer一样对所有回调使用一个线程(不重叠时(?

(这个问题不包括差异(

线程.睡眠与任务.延迟

MSDN 上的文档令人失望,但使用 Reflector 反编译Task.Delay提供了更多信息:

public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
    if (millisecondsDelay < -1)
    {
        throw new ArgumentOutOfRangeException("millisecondsDelay", Environment.GetResourceString("Task_Delay_InvalidMillisecondsDelay"));
    }
    if (cancellationToken.IsCancellationRequested)
    {
        return FromCancellation(cancellationToken);
    }
    if (millisecondsDelay == 0)
    {
        return CompletedTask;
    }
    DelayPromise state = new DelayPromise(cancellationToken);
    if (cancellationToken.CanBeCanceled)
    {
        state.Registration = cancellationToken.InternalRegisterWithoutEC(delegate (object state) {
            ((DelayPromise) state).Complete();
        }, state);
    }
    if (millisecondsDelay != -1)
    {
        state.Timer = new Timer(delegate (object state) {
            ((DelayPromise) state).Complete();
        }, state, millisecondsDelay, -1);
        state.Timer.KeepRootedWhileScheduled();
    }
    return state;
}

基本上,此方法只是包装在任务内部的计时器。所以是的,你可以说它就像计时器一样。

不,Task.Delay不会阻止当前线程。它可以用来阻止它,但它不能自己做,在实践中很少用作同步阻止程序。它实际所做的只是返回一个将在指定时间后完成的Task

Task task = Task.Delay(1000); // The task will complete after 1,000 milliseconds.

通常,然后在async方法中使用 await 关键字异步等待此任务:

await task; // Suspends the async method, but doesn't block the thread.

await 关键字暂停当前执行流(异步方法(,直到等待完成。执行流挂起时,不会阻塞任何线程。

还可以使用同步Wait方法阻止当前线程,直到任务完成。

task.Wait(); // Blocks the thread.

如果你想看到一个实验性演示,证明await Task.Delay()不会阻塞线程,这里有一个。下面的程序创建了大量任务,其中每个任务都在内部等待一个Task.Delay(1000)。然后在控制台中打印当前进程使用的线程数,最后等待所有任务:

Task[] tasks = Enumerable.Range(1, 100_000).Select(async _ =>
{
    await Task.Delay(1000);
}).ToArray();
Console.WriteLine($"Tasks: {tasks.Count(t => t.IsCompleted):#,0} / {tasks.Length:#,0}");
Thread.Sleep(500);
Console.WriteLine($"Threads.Count: {Process.GetCurrentProcess().Threads.Count:#,0}");
await Task.WhenAll(tasks);
Console.WriteLine($"Tasks: {tasks.Count(t => t.IsCompleted):#,0} / {tasks.Length:#,0}");

输出:

Tasks: 0 / 100,000
Threads.Count: 9
Tasks: 100,000 / 100,000

现场演示。

该程序仅在 1 秒后完成,并报告在其高峰期它总共使用了 9 个线程。如果 100,000 个任务中的每一个都阻塞了一个线程,我们预计此时会使用 100,000 个线程。显然这没有发生。