隐式与显式 .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
所以当我在任务管理器中查看我的套接字服务器解决方案时,我注意到应用程序正在创建和销毁线程(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