使用Task.Delay()会产生什么成本?

21

我在考虑在事件驱动逻辑中的MMO游戏服务器中使用C#async \ await。假设有数千个实体正在执行一些已知持续时间的工作。因此,我想为我的每个游戏对象调用 Time.Delay() 。 (这是与每个游戏对象某些 Update()调用的常见无限循环相反的方法。)

有人知道 Task.Delay() 如何实现吗? 它是否使用计时器? 它对系统资源的负担重吗?

同时产生数千个 Task.Delay() 调用是否适用?


4
因为Thread.Sleep不是异步的? - It'sNotALie.
Task.Delay是什么?这没有意义。如果我想要延迟一个线程,我现在就想要它,而不是生成另一个线程告诉一个线程停止。 - Cole Tobin
@Cole 的意思是让线程在恢复之前等待一段时间。重点不是延迟线程。 - It'sNotALie.
3
@ColeJohnson,使用await/async会导致调用Task.Delay()的代码似乎立即返回,并且在延迟结束后,延迟后面的代码将恢复执行。只要调用它的方法本身是async的,它就是非阻塞的。 - Matthew Watson
@MatthewWatson - 这与 Sleep() 调用有何不同?我不明白 :( - Martin James
1
@It'sNotALie。因为调用Thread.Sleep时传入0或1以外的参数总是一个很大的设计问题。 - Alex Zhukovskiy
2个回答

24

Task.Delay 实现如下:

public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
  //error checking
  Task.DelayPromise delayPromise = new Task.DelayPromise(cancellationToken);
  if (cancellationToken.CanBeCanceled)
    delayPromise.Registration = cancellationToken.InternalRegisterWithoutEC((Action<object>) (state => ((Task.DelayPromise) state).Complete()), (object) delayPromise);
  if (millisecondsDelay != -1)
  {
    delayPromise.Timer = new Timer((TimerCallback) (state => ((Task.DelayPromise) state).Complete()), (object) delayPromise, millisecondsDelay, -1);
    delayPromise.Timer.KeepRootedWhileScheduled();
  }
  return (Task) delayPromise;
}

它肯定使用计时器。 它们在称为DelayPromise的类中使用。 这是该类的实现:

private sealed class DelayPromise : Task<VoidTaskResult>
{
  internal readonly CancellationToken Token;
  internal CancellationTokenRegistration Registration;
  internal Timer Timer;

  internal DelayPromise(CancellationToken token)
  {
    this.Token = token;
  }

  internal void Complete()
  {
    if (!(this.Token.IsCancellationRequested ? this.TrySetCanceled(this.Token) : this.TrySetResult(new VoidTaskResult())))
      return;
    if (this.Timer != null)
      this.Timer.Dispose();
    this.Registration.Dispose();
  }
}

它确实使用了一个定时器,但对我来说似乎不是什么值得担心的事情。定时器只是回调到完成方法,它所做的就是检查它是否被取消了,如果是则取消它,否则返回结果。在我看来这很好。


1
我其实很惊讶它为每个延迟创建一个新的计时器对象。我本以为它会使用一个带有一个计时器的增量队列。 - Martin James
1
如MSDN文档所述:“System.Threading.Timer是一个简单、轻量级的计时器,它使用回调方法,并由线程池线程提供服务。” 对我来说,“轻量级”和“线程池线程”似乎是不兼容的概念。 - Anton Petrov
6
我相信实际(未经管理的)实现确实使用了一个 delta-queue。但是,如果您有许多“Delay”调用,您将会创建很多垃圾,并且这不是最有效的解决方案。如果您的“成千上万的实体”每秒钟要执行多个操作,我建议考虑其他替代方案。 - Stephen Cleary
1
我指的是CLR的一部分System.Timers.Timer实现。而且我是根据多年前查阅的记忆在说,可能有错。 - Stephen Cleary
9
.NET计时器实现包装了Win32定时器队列,这是一个增量队列,在线程池上触发事件。更多信息请参见:https://msdn.microsoft.com/en-us/library/ms686796(v=VS.85).aspx - Chuu
显示剩余3条评论

0
在 .NET Core 中,Task.Delay 是通过 TimerQueue 实现的。它不会每次都创建一个新的 Timer。从我所看到的 source code,它实际上创建了 N 个“队列”,其中 N 是 CPU 核心数。然后为每个队列使用 1 个计时器。
这比 .NET Framework 的方法更轻量级,但仍然会执行大量代码,每次调用 Task.Delay 都是如此。我希望有更好的方式。
P.S. 这只是为那些使用谷歌搜索的人提供更更新的版本。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接