我怎么处理异步任务我不想等待?

Gen*_*ror 11 c# asynchronous synchronizationcontext task-parallel-library async-await

我正在编写一个多人游戏服务器,我正在研究新的C#async/await功能可以帮助我的方法.服务器的核心是一个循环,它尽可能快地更新游戏中的所有演员:

while (!shutdown)
{
    foreach (var actor in actors)
        actor.Update();

    // Send and receive pending network messages
    // Various other system maintenance
}
Run Code Online (Sandbox Code Playgroud)

这个循环需要处理数千个演员并每秒更新多次以保持游戏顺利运行.有些演员偶尔会在更新函数中执行缓慢的任务,例如从数据库中获取数据,这是我想要使用异步的地方.一旦检索到该数据,actor就想要更新游戏状态,这必须在主线程上完成.

由于这是一个控制台应用程序,我计划编写一个SynchronizationContext,它可以将挂起的委托分派给主循环.这允许这些任务在完成后更新游戏,并允许将未处理的异常抛入主循环.我的问题是,如何编写异步更新功能?这非常好用,但打破了不使用async void的建议:

Thing foo;

public override void Update()
{
    foo.DoThings();

    if (someCondition) {
        UpdateAsync();
    }
}

async void UpdateAsync()
{
    // Get data, but let the server continue in the mean time
    var newFoo = await GetFooFromDatabase();

    // Now back on the main thread, update game state
    this.foo = newFoo;
}
Run Code Online (Sandbox Code Playgroud)

我可以使Update()异步并将任务传播回主循环,但是:

  • 我不想为数千个永远不会使用它的更新增加开销.
  • 即使在主循环中,我也不想等待任务并阻止循环.
  • 等待任务将导致死锁,因为它需要在等待线程上完成.

我怎么办这些我无法等待的任务?我唯一想知道他们已经完成的时间是关闭服务器的时候,但是我不想收集可能需要数星期更新的每一项任务.

aca*_*lon 9

我的理解是,它的关键在于你想要:

while (!shutdown)
{
    //This should happen immediately and completions occur on the main thread.
    foreach (var actor in actors)
        actor.Update(); //includes i/o bound database operations

    // The subsequent code should not be delayed
   ...
}
Run Code Online (Sandbox Code Playgroud)

while循环在主控制台线程中运行的位置.这是一个紧密的单线程循环.您可以并行运行foreach,但是您仍然需要等待运行时间最长的实例(用于从数据库获取数据的i/o绑定操作).

等待异步不是此循环中的最佳选项,您需要在线程池上运行这些i/o数据库任务.在线程池上,async await对于释放池线程很有用.

那么,接下来的问题是如何将这些完成返回到主线程.好吧,看起来你需要一些与主线程上的消息泵相当的东西.有关如何执行此操作的信息,请参阅此帖子,尽管这可能有点沉重.你可以只有一个完成队列,你可以通过while循环检查每个传递中的主线程.您可以使用其中一个并发数据结构来执行此操作,以便它是所有线程安全的,然后在需要设置时设置Foo.

似乎有一些空间来合理化这种对演员和线程的轮询,但是在不知道应用程序的细节的情况下很难说.

几点: -

  • 如果您没有在某个任务上等待,则主控制台线程将退出,您的应用程序也将退出.详情请见此处.

  • 正如您所指出的那样,等待异步不会阻塞当前线程,但它确实意味着await之后的代码只会在await完成时执行.

  • 完成可能会或可能不会在调用线程上完成.您已经提到了同步上下文,因此我不会详细介绍.

  • 控制台应用程序上的同步上下文为空.请参阅此处获取信息.

  • Async并不适用于即发即弃型操作.

对于火灾和遗忘,您可以根据您的情况使用以下选项之一:

  • 使用Task.Run或Task.StartNew.请看这里的差异.
  • 对于在您自己的线程池下运行的长时间运行方案,请使用生产者/消费者类型模式.

请注意以下事项: -

  • 您将需要处理生成的任务/线程中的异常.如果您没有观察到任何异常,您可能想要处理这些,甚至只是为了记录它们的出现.请参阅有关未观察到的异常的信息.
  • 如果您的进程在这些长时间运行的任务在队列中或者启动它们将不会运行时死亡,那么您可能需要某种持久性机制(数据库,外部队列,文件)来跟踪这些操作的状态.

如果您想了解这些任务的状态,那么您需要以某种方式跟踪它们,无论是内存列表,还是通过查询自己的线程池的队列或查询持久性机制.关于持久性机制的好处在于它对崩溃具有弹性,在关机期间你可以立即关闭,然后在重新启动时获取最终结果(这当然取决于任务在其中运行的重要性)某个时间段).