【问题标题】:What does determine the amount of time an "await" takes?什么决定了“等待”所需的时间?
【发布时间】:2019-06-14 21:09:44
【问题描述】:

我有以下代码从 TCP 流中读取字节(我已删除错误检查):

/// <summary>Awaitable. Reads a certain amount of bytes from a network stream. Returns false
/// on error</summary>
async Task<bool> readBytes (NetworkStream stream, byte []buffer, int totalBytes)
{
    int bytesRead = 0;

    while (true)
    {
        var br = await stream.ReadAsync (buffer, bytesRead, totalBytes - bytesRead);
        if (br == 0) return false;      // closed stream

        bytesRead += br;
        if (bytesRead >= totalBytes) return true;
    }
}

然后我在外部使用它来接收4个字节的数据长度和数据主体(伪代码,我已经删除了错误检查和分配,只是为了提供一个想法):

success = await readBytes(stream, header, 4);
success = await readBytes(stream, data, dataLength);

presentDataToApp(data, dataLength);

客户端/服务器必须几乎实时交互(这是一个客户端应用程序调整多个参数的游戏,例如灯光)。例如,客户端有一个 WinForms 滑块,并将所有更改传输到服务器应用程序。移动该滑块时,一秒钟内可能会有很多更新(从我的日志来看,它每秒发送数据大约 100 次)。

只要服务器(运行我粘贴的代码)通过 Visual Studio 运行,此代码就可以很好地实时运行。但是,当我在没有 Visual Studio(或使用 Ctrl+F5)的情况下运行服务器应用程序时,通信开始严重滞后。

为了放弃异步/等待问题,我克隆了 comms 类并通过线程运行它,删除了所有异步/等待函数。该代码现在可以在 Visual Studio 和独立环境中实时运行(因此这是与 async/await 相关的问题)。

为什么在这种情况下 async/await 通信滞后? (一秒钟内肯定有很多等待)。什么决定了在没有准备好数据的等待之后等待的时间量? (我以为是计时器分辨率,但无论我如何执行服务器应用程序,它在我的系统上似乎都是 1 毫秒)。

【问题讨论】:

  • 在发布模式下运行会发生什么?
  • async/await 不是一个孤立的问题。向我们展示您如何称呼它,以实现所有其他异步或线程。
  • 我的第一个疯狂猜测 - 您的“发送”代码饿死了您的“接收”代码。你能检查一下,在没有 Visual Studio 的情况下每秒发送多少次?还可以尝试设置硬限制(比如每秒 50 次,因为我怀疑你需要更多),看看是否有帮助。
  • 听起来你的窗口的消息循环可能已经饱和了,因为这就是实际上将延续带回 UI 线程的方式。
  • 另外,一点旁注。您的计时器分辨率实际上不是 1 毫秒,Windows 不会为您提供比大约 13-14 毫秒更好的分辨率。

标签: c# asynchronous async-await


【解决方案1】:

客户端/服务器必须几乎实时交互

在您同时控制客户端和服务器的任何情况下,我总是推荐 SignalR。与原始套接字相比,针对 SignalR 进行编程要容易得多。

只要服务器通过 Visual Studio 运行,此代码就可以很好地实时运行。但是,当我在没有 Visual Studio(或使用 Ctrl+F5)的情况下运行服务器应用程序时,通信开始严重滞后。

这非常奇怪。 VS 正在使您的代码运行更快?我对此没有任何解释。

什么决定了没有准备好数据的等待后等待的时间量?

这里有几件事可能会产生影响。

第一个是await 捕获一个上下文并在该上下文上恢复。如果它是 UI 上下文,这尤其是一个问题。 At 100 updates per second, you're reaching the limit of what's practical。如果你的awaits 正在 UI 线程上恢复,你可以通过使用ConfigureAwait(false) 来避免这种“千刀万剐的性能死亡”。如果确实是await 导致了减速,请花时间查看Zen of Async video

第二个是内存流失很多。 readBytes 至少需要为每个数据包使用一个单独的数组(假设您重用了标头数组),并且周围还有许多 Task 实例。 如果 分析表明问题是内存流失,那么consider using the new Memory&lt;T&gt;-based socket APIs that avoid the byte arrays, as well as the new ValueTask&lt;T&gt;-based APIs which avoid the Task allocations when the data is already arrived。例如,.NET Core 2.1 Stream API ValueTask&lt;int&gt; ReadAsync(Memory&lt;byte&gt; buffer, CancellationToken cancellationToken); 如果分析表明问题是内存流失但您还没有使用 .NET Core 2.1,那么您可以使用 SocketAsyncEventArgs APIs

【讨论】:

  • 感谢您的所有回答。问题是我从 UI 线程调用该代码,尽管它不需要从 UI 调用。阅读第一个链接让我认为这可能是问题所在。让我困惑的是 VS 的行为。
猜你喜欢
  • 1970-01-01
  • 2021-08-02
  • 1970-01-01
  • 2022-10-15
  • 2021-11-19
  • 2020-09-12
  • 1970-01-01
  • 1970-01-01
  • 2017-10-21
相关资源
最近更新 更多