隐式与显式 .NET 线程

Implicit vs. Explicit .NET Threading

所以当我在任务管理器中查看我的套接字服务器解决方案时,我注意到应用程序正在创建和销毁线程(9 到 10 到 15 回到 10)。 MySQL 连接器可以使用其中一些,但似乎登录服务器会创建一个新线程。

我正在使用异步套接字接受连接,将其传递给 PacketHandler 实例,同时监听并发连接。我的问题是,我如何判断哪些代码将创建一个新线程?我从来没有明确地写过需要创建一个线程,但这似乎是使用异步套接字的结果。我知道您在创建线程时应该比较保守(核心数 * 2 = 目标线程数),但是当您不知道什么代码会自然地创建新线程时,这是一项艰巨的任务。

正确的异步代码只使用线程进行回调,通常只需要线程池线程,除非你做错了什么,否则不需要启动新的线程池线程。

MS.NET 中当前的套接字实现确实属于这种情况。他们在异步事件上注册回调,并在该回调期间使用线程池线程。除非您在回调中执行同步 I/O 操作,否则在线程池上创建更多线程毫无意义。

不要查看任务管理器 - 查看调试器。您看到的线程很可能与您正在使用的库无关。在 .NET 中有相当多的基础结构线程被创建和处理——特别是垃圾收集器。检查这些线程上的堆栈跟踪,您就会知道它们的作用。

编辑: 在这种情况下,似乎 MySQL 连接器确实没有使用异步 I/O。它只是假装同步 I/O 是使用多线程的异步 - 换句话说,异步 API 是完全无用的胡说八道 :) 而且因为它们使用线程池线程进行同步 I/O,你会遇到很多问题。要么使用不同的库,要么避免异步 API - 它在骗你。

遗憾的是,MySQL Connector 不提供真正的异步方法。它的 Begin/End 方法通过 wrapping the synchronous version 在线程中伪造异步执行:

public IAsyncResult BeginExecuteReader(CommandBehavior behavior)

{

  if (caller != null)

    Throw(new MySqlException(Resources.UnableToStartSecondAsyncOp));



  caller = new AsyncDelegate(AsyncExecuteWrapper);

  asyncResult = caller.BeginInvoke(1, behavior, null, null);

  return asyncResult;

}

其中 AsyncExecuteWrapper 是:

internal object AsyncExecuteWrapper(int type, CommandBehavior behavior)

{

  thrownException = null;

  try

  {

    if (type == 1)

      return ExecuteReader(behavior);

    return ExecuteNonQuery();

  }

  catch (Exception ex)

  {

    thrownException = ex;

  }

  return null;

}

结果,他们浪费了一个等待响应的线程。三年前提交了一个错误 but never got a real answer

这就是为什么这个选择 MySQLConnector project was created that provides true asynchronous operations as well as .NET Core support,eg this method

    internal async Task<int> ExecuteNonQueryAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
    {
        using (var reader = (MySqlDataReader) await ExecuteReaderAsync(CommandBehavior.Default, ioBehavior, cancellationToken).ConfigureAwait(false))
        {
            do
            {
                while (await reader.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false))
                {
                }
            } while (await reader.NextResultAsync(ioBehavior, cancellationToken).ConfigureAwait(false));
            return reader.RecordsAffected;
        }
    }

你会发现ReadAsync也是一个合适的异步方法

区别很明显,尤其是在 Web 应用程序中,因为它允许您使用 更小的 VM 实例来处理相同的流量。或者同一个 VM 可以处理更多流量。无论如何,价格差异是真实存在的。

发生这种情况是因为异步网络操作实际上已卸载到驱动程序或主机。来自 IO 线程池的线程仅在网络驱动程序向应用程序传递响应时使用。在半虚拟化的情况下,卸载可以一直到主机。

另一方面,伪造的同步操作会阻塞,甚至可能导致忙等待。这是因为同步原语旨在用于... 同步访问 共享资源。挂起线程是有成本的,所以等待一个原语首先从一个自旋锁开始,并且只在一定时间过去后挂起线程。

这就是为什么不考虑异步的 Web 应用程序最终会在等待远程响应时消耗大量 CPU