我正在尝试理解 David Fowler 所说的关于
Task.Factory.StartNew
+ TaskCreationOptions.LongRunning
这里。
💡注意:不要将 TaskCreationOptions.LongRunning 与异步代码一起使用,因为这将创建一个新线程,该线程将在第一次等待后被销毁。
我知道在这种情况下使用
Task.Run
或 Task.Factory.StartNew
是没有意义的,因为 SendLoopAsync 和 ReceiveLoopAsync 是完全异步的。我还知道,如果这些方法之一中有一个耗时的同步部分,则 Task.Run/Task.Factory.StartNew 应该位于该方法内。
大卫·福勒在他的声明中是什么意思?异步任务中不应该有
TaskCreationOptions.LongRunning
吗?或者他的意思是 SendLoopAsync/ReceiveLoopAsync 不应该是异步的?我还知道 TaskCreationOptions.LongRunning 意味着任务将立即启动,这与调度程序安排的普通任务不同,并且可能需要一些时间才能结束。当同时启动多个连接时,您会注意到这种行为,这会导致发送和接收循环启动时出现明显的延迟。
public async Task StartAsync(CancellationToken cancellationToken)
{
_ = Task.Factory.StartNew(_ => SendLoopAsync(cancellationToken), TaskCreationOptions.LongRunning, cancellationToken);
_ = Task.Factory.StartNew(_ => ReceiveLoopAsync(cancellationToken), TaskCreationOptions.LongRunning, cancellationToken);
}
private async Task SendLoopAsync()
{
await foreach (var message in _outputChannel.Reader.ReadAllAsync(_cancellationSource?.Token))
{
if (_clientWebSocket.State == WebSocketState.Open)
{
await _clientWebSocket.SendAsync(message.Data.AsMemory(), message.MessageType, true, CancellationToken.None).ConfigureAwait(false);
}
}
}
David Fowler 意味着
SendLoopAsync
/ReceiveLoopAsync
不应该是异步的。如果任务要使用启动线程持续以纳秒为单位的持续时间,则以 LongRunning
方式启动任务是没有意义的。发明 ThreadPool
就是为了准确处理这些类型的情况。如果 ThreadPool
由于已饱和而响应不够,那么尝试找到饱和原因并修复它会更合乎逻辑,而不是绕过 ThreadPool
,并在每次有一些线程时创建新线程需要完成微秒级的工作。
这里演示了
LongRunning
与异步结合时会发生什么:
Stopwatch stopwatch = Stopwatch.StartNew();
Thread workerThread = null;
ConcurrentQueue<(string, long, System.Threading.ThreadState)> entries = new();
Task<Task> taskTask = Task.Factory.StartNew(async () =>
{
workerThread = Thread.CurrentThread;
entries.Enqueue(("A", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
await Task.Delay(500);
entries.Enqueue(("D", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
}, default, TaskCreationOptions.LongRunning, TaskScheduler.Default);
taskTask.Wait();
entries.Enqueue(("B", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
workerThread.Join();
entries.Enqueue(("C", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
await taskTask.Unwrap();
entries.Enqueue(("E", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
foreach (var (title, elapsed, state) in entries)
Console.WriteLine($"{title } after {elapsed,3} msec worker thread is {state}");
输出:
A after 2 msec worker thread is Background
B after 6 msec worker thread is Background, Stopped
C after 6 msec worker thread is Stopped
D after 507 msec worker thread is Stopped
E after 507 msec worker thread is Stopped
工作线程的生命周期最多为6毫秒。它真正要做的就是实例化一个异步状态机,并使用
System.Threading.Timer
组件安排回调。对于如此微小的工作量来说,6 毫秒对我来说就像是一个亿万年。这 6 毫秒很可能用于线程间通信以及线程的创建和销毁。