何时使用Task。延迟,什么时候使用Thread.Sleep?

什么时候使用的任务。延迟而不是线程。睡眠,有好的规则吗?

  • 具体来说,是否存在一个最小值来保证其中一个比另一个更有效?
  • 最后,自从任务。延迟导致异步/等待状态机上的上下文切换,是否有使用它的开销?
376983 次浏览

当你想阻塞当前线程时,使用Thread.Sleep

当您想要一个逻辑延迟而不阻塞当前线程时,请使用await Task.Delay

效率不应该是这些方法的首要考虑因素。它们在现实世界中的主要用途是作为I/O操作的重试计时器,其数量级为秒而不是毫秒。

如果当前线程被杀死,你使用Thread.Sleep并且它正在执行,那么你可能会得到一个ThreadAbortException。 使用Task.Delay,您可以始终提供一个取消令牌并优雅地终止它。这是我选择Task.Delay的原因之一。看到# EYZ2 < / p >

我也同意在这种情况下效率不是最重要的。

Task.DelayThread.Sleep之间最大的区别是,Task.Delay旨在异步运行。在同步代码中使用Task.Delay没有意义。在异步代码中使用Thread.Sleep是一个非常糟糕的主意。

通常你会用await关键字调用Task.Delay():

await Task.Delay(5000);

或者,如果你想在延迟之前运行一些代码:

var sw = new Stopwatch();
sw.Start();
Task delay = Task.Delay(5000);
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
await delay;

猜猜这会打印什么?运行0.0070048秒。 如果我们将await delay移到Console.WriteLine上面,它将输出Running for 5.0020168秒

让我们看看与Thread.Sleep的区别:

class Program
{
static void Main(string[] args)
{
Task delay = asyncTask();
syncCode();
delay.Wait();
Console.ReadLine();
}


static async Task asyncTask()
{
var sw = new Stopwatch();
sw.Start();
Console.WriteLine("async: Starting");
Task delay = Task.Delay(5000);
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
await delay;
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
Console.WriteLine("async: Done");
}


static void syncCode()
{
var sw = new Stopwatch();
sw.Start();
Console.WriteLine("sync: Starting");
Thread.Sleep(5000);
Console.WriteLine("sync: Running for {0} seconds", sw.Elapsed.TotalSeconds);
Console.WriteLine("sync: Done");
}
}

试着预测一下它会打印什么…

< p >异步:从< br > async:运行0.0070048秒
同步:从< br > async: Running for 5.0119008 seconds
. async: Running for 5.0119008 seconds
. async: Running for 5.0119008 seconds 异步:做< br > sync: Running for 5.0020168 seconds
. sync: Running for 5.0020168秒 sync: Done

此外,有趣的是,Thread.Sleep更准确,毫秒精度不是问题,而Task.Delay可以花费15-30毫秒。与ms精度相比,这两个函数的开销是最小的(如果您需要更精确的东西,请使用Stopwatch类)。Thread.Sleep仍然捆绑你的线程,Task.Delay释放它做其他工作,而你等待。

我想补充一些东西。 实际上,Task.Delay是一个基于定时器的等待机制。如果您查看,您会发现一个对Timer类的引用,该类负责延迟。另一方面,Thread.Sleep实际上使当前线程进入睡眠状态,这样你只是阻塞和浪费了一个线程。在异步编程模型中,如果你想让某些事情(延续)在延迟后发生,你应该总是使用Task.Delay()

我的观点,

Task.Delay()是异步的。它不会阻塞当前线程。您仍然可以在当前线程中执行其他操作。它返回一个任务返回类型(Thread.Sleep()不返回任何东西)。您可以在另一个耗时的过程之后检查该任务是否完成(使用Task.IsCompleted属性)。

Thread.Sleep()没有返回类型。它是同步的。在线程中,除了等待延迟完成之外,您实际上不能做任何事情。

至于在现实生活中的使用,我已经编程15年了。我从未在产品代码中使用过Thread.Sleep()。我找不到任何用例。 也许这是因为我主要做的是web应用程序开发

Delayed可能是Task.Delay更好的名称,因为它不会延迟现有的任务,而是创建一个新的“延迟”任务,另一方面可以等待,并可能导致当前任务主体挂起。它本质上是一个Timer,但没有回调/主体。

等待一个延迟的任务在异步消息队列中创建一个新项目,并且不阻塞任何线程。调用await的同一个线程将继续处理其他任务(如果还有其他任务的话),并将在超时后(或队列中的前面项完成时)返回到await点。底层任务使用线程——可以在单个线程中调度和执行许多任务。另一方面,如果你碰巧调用了Thread.Sleep(),线程就会阻塞,也就是说,它会在请求的时间内停止工作,并且不会处理队列中的任何异步消息。

在。net中有两种主要的并行方法。旧版本有线程、线程池等。而新的,基于任务,async/await, TPL。根据经验,不要将这两个领域的api混合使用。

值得一提的是Thread.Sleep(1)将更快地触发GC。

这纯粹是基于我的&团队成员的观察。我们假设 你有为每个特定的请求创建新任务的服务 (约。200-300个正在进行中),该任务包含许多弱引用 在流。任务像状态机一样工作,所以我们发射 Thread.Sleep(1)在改变状态,通过这样做,我们设法优化 应用程序中的内存利用率-就像我之前说的-这 将使GC发射更快。这没有太大的区别 低内存消耗服务(<1GB)。

我和一位同事为此争论了很长时间,他向我证明,在上面的答案目前所显示的范围之外,还有显著的差异。如果你await Task.Delay(SomeMilliseconds),你实际上可以释放堆栈上除了你的直接父对象之外的调用者:

using System;
using System.Threading;
using System.Threading.Tasks;


namespace ConsoleApp1
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Started " + Thread.CurrentThread.ManagedThreadId);
DoSomething1();
Console.WriteLine("Finished " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(6000);
}


static async void DoSomething1()
{
Console.WriteLine("DoSomething1 Started " + Thread.CurrentThread.ManagedThreadId);
var result = await DoSomething2();
Console.WriteLine("DoSomething1 Finished " + Thread.CurrentThread.ManagedThreadId);
}


static async Task<int> DoSomething2()
{
Console.WriteLine("DoSomething2 Started " + Thread.CurrentThread.ManagedThreadId);


await Task.Delay(5000);         // Will block DoSomething1 but release Main
//Thread.Sleep(5000);           // Will block everything including Main
//await Task.FromResult(5);     // Will return immediately (just for comparison)
//await Task.Delay(0);          // What will it do, can you guess?


Console.WriteLine("DoSomething2 Finished " + Thread.CurrentThread.ManagedThreadId);
return 0;
}
}
}

试运行这段代码,观察使用DelaySleep的不同效果。解释超出了这个答案的范围,但可以总结为“异步函数不会启动另一个线程,直到他们等待一些不能立即运行(或结果确定)的东西”。输出如下:

Started 1
DoSomething1 Started 1
DoSomething2 Started 1
Finished 1
DoSomething2 Finished 4
DoSomething1 Finished 4

这不是关于DoSomething1();Main被火和遗忘。你可以使用Sleep来证明这一点。还要注意,当DoSomething2 "returns"从任务。延迟,它在另一个线程上运行。

这个东西比我给它的信用要聪明得多,相信await只是开始了一个新的线程来做事情。我仍然没有假装完全理解,但上面的反直觉结果表明,在底层有更多的事情要做,而不仅仅是启动线程来运行代码。

在异步程序中,两者的区别

await task.Delay()
//and
thread.sleep

在一个简单的应用程序中,一个可能更可取消,一个可能更准确,一个可能更快……但在一天结束的时候,两者都做同样的事情,他们阻止执行代码。..

以下是调查结果:

1 00:00:00.0000767
Not Delayed.
1 00:00:00.2988809
Delayed 1 second.
4 00:00:01.3392148
Delayed 3 second.
5 00:00:03.3716776
Delayed 9 seconds.
5 00:00:09.3838139
Delayed 10 seconds
4 00:00:10.3411050
4 00:00:10.5313519

从这段代码:

var sw = new Stopwatch();
sw.Start();
Console.WriteLine($"{sw.Elapsed}");
var asyncTests = new AsyncTests();


var go1 = asyncTests.WriteWithSleep();
var go2 = asyncTests.WriteWithoutSleep();


await go1;
await go2;
sw.Stop();
Console.WriteLine($"{sw.Elapsed}");
        

Stopwatch sw1 = new Stopwatch();
Stopwatch sw = new Stopwatch();
public async Task WriteWithSleep()
{
sw.Start();
var delayedTask =  Task.Delay(1000);
Console.WriteLine("Not Delayed.");
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
await delayedTask;
Console.WriteLine("Delayed 1 second.");
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
Thread.Sleep(9000);
Console.WriteLine("Delayed 10 seconds");
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
sw.Stop();
}
public async Task WriteWithoutSleep()
{
await Task.Delay(3000);
Console.WriteLine("Delayed 3 second.");
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
await Task.Delay(6000);
Console.WriteLine("Delayed 9 seconds.");
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
}

睡眠的作用与即时等待相同,只是它会阻塞线程。分配给var的任务在最终等待时可能会导致线程切换。在这个例子中,代码从线程1开始,然后为writewithsleep()创建线程5,但继续在线程1上为ThreadWithSleep()执行,直到等待delayedTask。此时,线程1的代码流到线程4,Main中的进一步执行现在在线程4上;因为没有更好的词,线程1被扔掉了。

以上所有的答案都很有价值。然而,在一个简单的控制台应用程序中,它似乎并不重要,除非在你使用的几次运行过程中,如果你立即等待你的Task.Delay()并且不打算使用取消令牌;

在一个复杂的应用程序中,让线程进入睡眠状态,还是因为创建任务而从一个线程跳到另一个线程,然后等待它们,还是立即等待,这是需要考虑的问题。

最后,放入Process.GetCurrentProcess(). threads。控制台应用程序(至少是我的)开头的Count在调试器模式下产生了13个线程。在等待调用之后,我在visual studio的调试器模式下有17个线程。我曾经读到consoleApp只有3个线程,其余的都是调试器线程,但是在没有调试的情况下运行consoleApp会导致8个线程和14个线程。在visual studio外部运行它的结果是8个线程和14个线程。

复制代码并粘贴它之后,线程数是相同的,8,14,所有内容都停留在线程4和5上。第二个线程。Sleep和task.delay不会导致线程跳转。所有这些研究都是为了提出:while线程。Sleep会阻塞一个线程,task.delay不会,并且有一个取消令牌,除非你的应用程序非常复杂,它真的不重要,因为表面上:task.delay和thread。睡眠也做着同样的事情。