c# 4.5 - 一个主要做数据库插入的 TCP 服务器是否应该在一个任务上启动每个客户端

c# 4.5 - Should a TCP Server, mainly doing database inserts, start each client on a Task

我的理解是 async await 用于 IO(网络、数据库等),并行任务用于 cpu。

注意:这段代码为了简洁起见有点苛刻post。

我有一个用 c# 创建的 windows 服务,它具有以下代码

while (true)
{
    var socket = await tcpListener.AcceptSocketAsync();
    if (socket == null) { break; }

    var client = new RemoteClient(socket);
    Task.Run(() => client.ProcessMessage());
}

在 RemoteClient 中 class ProcessMessage 方法执行此操作

byte[] buffer = new byte[4096];
rawMessage = string.Empty;
while (true)
{
    Array.Clear(buffer, 0, buffer.Length);
    int bytesRead = await networkStream.ReadAsync(buffer, 0, buffer.Length);
    rawMessage += (System.Text.Encoding.ASCII.GetString(buffer).Replace("[=11=]", string.Empty));

    if (bytesRead == 0 || buffer[buffer.Length - 1] == 0)
    {
        StoreMessage();
        return;
    }
}

所以我的 I/O 工作是异步进行的。但我的顾虑和问题是使用 Task.Run 来开始工作 我还在创建一个块吗?

我正在尝试建立一个 TCP 连接并尽快释放它,以便扩展到大量连接。

我觉得我在这里混合范式。

谢谢

My understanding is that async await is for IO (network, db, etc) and parallel task is for cpu.

我会说理解不正确。 async/await 用于任何异步操作,无论是 I/O 还是 CPU 绑定。

…my concern and my question is in using Task.Run to kick off the work am I still creating a block?

"A block"?您认为否则会创建什么样的块?


就个人而言,我不会那样写代码。 accept 操作已经在线程池线程中完成(或在同一线程中同步完成),即来自 IOCP 线程池的线程。为该线程上的连接设置一些初始条件,然后从那里启动 I/O 是非常好的。没有理由在另一个线程上排队工作。

所以我写代码的方式是这样的:

async Task ProcessMessage()
{
    byte[] buffer = new byte[4096];
    rawMessage = string.Empty;
    while (true)
    {
        Array.Clear(buffer, 0, buffer.Length);
        int bytesRead = await networkStream.ReadAsync(buffer, 0, buffer.Length);
        rawMessage += (System.Text.Encoding.ASCII.GetString(buffer).Replace("[=10=]", string.Empty));

        if (bytesRead == 0 || buffer[buffer.Length - 1] == 0)
        {
            StoreMessage();
            return;
        }
    }
}

那么在您的服务中:

while (true)
{
    var socket = await tcpListener.AcceptSocketAsync();
    if (socket == null) { break; }

    var client = new RemoteClient(socket);
    var _ = client.ProcessMessage();
}

备注:

  • 虚拟 _ 变量只是为了防止编译器警告您有关被忽略的、未等待的异步 return)
  • 由于您忽略了 returned Task 对象,因此您不会收到抛出的异常。因此,您应该为 ProcessMessage() 方法本身添加适当的异常处理。
  • 我同意评论者 shr 关于清理的意见。您没有提供完整的代码示例,所以我们不知道例如StoreMessage() 方法可以。但是 presumably/hopefully 你在某个地方有逻辑可以正确而优雅地关闭连接并关闭套接字。