【发布时间】:2013-12-25 17:09:04
【问题描述】:
我正在开发一个可扩展的游戏服务器系统,并试图在这个新项目中过度使用 await/async。到目前为止,我设法遇到了几个陷阱。我已经找出了大多数解决方案,并开发了一种“通用实践”来编写异步方法,以便易于检测和避免这些陷阱。不幸的是,以下让我很头疼:
1) 示例:
async Task<int> RunSqlQuery()
{
DbCommand cmd = conn.CreateCommand();
...
cmd.ExecuteReader();
...
}
哇!出于几个原因,这是一种破坏。一,有一个 ExecuteReaderAsync(),可以解决这个问题。其次,此调用将阻塞和停止内部异步框架的工作线程。在桌面环境中不是太重要,但不幸的是,如果您的服务器每秒有 8000 个请求,这可能会严重损害您的性能,并且很难追踪导致问题的精确行。
2) 示例:
void ThisHurts()
{
someTask.GetAwaiter().GetResult();
}
async Task<int> RunQuery()
{
...
lock(something)
{
ThisHurts();
}
...
}
我承认这个看起来有点设计来证明我的观点,但是在处理大量代码时,这是一种难以避免的情况。基本上在锁中调用 await 是致命的。这是一场等待发生的灾难(字面意思)。有很多解释为什么,不管你信不信,它们是真的,构建类似上面的东西,如果你的服务器上有负载并且大量的竞争条件开始链接在一起,你的代码几乎会立即死亡......
所以这个问题是关于如何检测那些错误的 await 用法。有没有像 Resharper 这样的工具(它似乎不支持我想要的大部分内容)。我认为微软应该标记所有非阻塞方法,并强制人们以异步兼容的方式包装它们,或者在尝试在异步方法中调用它们时发出编译器错误。此外,他们可能会强迫您在所有未标记为非阻塞的方法前面添加一个新的“阻塞”关键字,以便您仍然可以使用它们,但会立即显示出耻辱。
到目前为止,我看到的异步框架的主要问题是误用只会在负载过重的情况下出现,这通常很难调试和跟踪,因为它的数据量很大,并且在您附加调试器的那一刻问题就消失了,因为负载消失了。太棒了;)
【问题讨论】:
-
tagged all non-blocking methods and force people to either wrap them in an async compatible way那是不可能的。如果你有阻塞方法,让它异步的唯一方法是完全重写它以使用非阻塞 IO。 -
这曾经被称为“设计错误”,之前没有听说过它被称为“灾难”。我想如果你在项目结束时做 TDD。
-
“很难找到导致问题的确切线路。”我不这么认为。 Profilers 可以向您展示您的代码在哪里花费了大量时间。如果你阻塞了很多,你会在那里看到那个方法。
-
另外,考虑到您不能在
lock中使用await,我发现很难想象情况2 会自然出现的情况。 -
嗯...不。标记与制作非阻塞无关。它是一个编译器信息,仅此而已。以异步兼容的方式包装是任何方式,所以并非不可能,哈哈。好吧,如果分析器可以区分阻塞和“等待”,那么可以,但到目前为止还没有尝试过。可能会出现第二种情况,因为您可以在锁中等待。只是不在其中,但没有什么可以阻止您在那里调用的 API 等待。
标签: c# .net async-await c#-5.0