Task.Factory.StartNew + TaskCreationOptions.LongRunning 说明
Task.Factory.StartNew + TaskCreationOptions.LongRunning explanation
我想了解大卫福勒对 Task.Factory.StartNew
+ TaskCreationOptions.LongRunning
here.
的看法
NOTE: Don't use TaskCreationOptions.LongRunning with async code as this will create a new thread which will be destroyed after first await.
我知道在这种情况下 Task.Run
或 Task.Factory.StartNew
没有意义,因为 SendLoopAsync 和 ReceiveLoopAsync 是完全异步的。我也知道,如果这些方法中的任何一个中有一个耗时的同步部分,那么 Task.Run/Task.Factory.StartNew 应该在该方法中。
David Fowler 在他的声明中是什么意思?异步任务中不应该有 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 means that the SendLoopAsync
/ReceiveLoopAsync
should not be async. There is no point at starting a task as LongRunning
, if this task is going to use the starting thread for a duration measured in nanoseconds. The ThreadPool
的发明就是为了准确处理这些类型的情况。如果 ThreadPool
由于已经饱和而响应不够,那么更合乎逻辑的做法是尝试找到饱和的原因并修复它,而不是绕过 ThreadPool
并每隔一段时间创建新线程你有一些 microseconds-worth 工作要做的时候。
下面是 LongRunning
与 async 结合使用时会发生什么的演示:
var stopwatch = Stopwatch.StartNew();
Thread workerThread = null;
List<(string, long, System.Threading.ThreadState)> entries = new();
Task<Task> taskTask = Task.Factory.StartNew(async () =>
{
workerThread = Thread.CurrentThread;
entries.Add(("A", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
await Task.Delay(500);
entries.Add(("D", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
}, default, TaskCreationOptions.LongRunning, TaskScheduler.Default);
taskTask.Wait();
entries.Add(("B", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
workerThread.Join();
entries.Add(("C", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
await taskTask.Unwrap();
entries.Add(("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 毫秒很可能用于 inter-thread 通信,以及线程的创建和销毁。
我想了解大卫福勒对 Task.Factory.StartNew
+ TaskCreationOptions.LongRunning
here.
NOTE: Don't use TaskCreationOptions.LongRunning with async code as this will create a new thread which will be destroyed after first await.
我知道在这种情况下 Task.Run
或 Task.Factory.StartNew
没有意义,因为 SendLoopAsync 和 ReceiveLoopAsync 是完全异步的。我也知道,如果这些方法中的任何一个中有一个耗时的同步部分,那么 Task.Run/Task.Factory.StartNew 应该在该方法中。
David Fowler 在他的声明中是什么意思?异步任务中不应该有 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 means that the SendLoopAsync
/ReceiveLoopAsync
should not be async. There is no point at starting a task as LongRunning
, if this task is going to use the starting thread for a duration measured in nanoseconds. The ThreadPool
的发明就是为了准确处理这些类型的情况。如果 ThreadPool
由于已经饱和而响应不够,那么更合乎逻辑的做法是尝试找到饱和的原因并修复它,而不是绕过 ThreadPool
并每隔一段时间创建新线程你有一些 microseconds-worth 工作要做的时候。
下面是 LongRunning
与 async 结合使用时会发生什么的演示:
var stopwatch = Stopwatch.StartNew();
Thread workerThread = null;
List<(string, long, System.Threading.ThreadState)> entries = new();
Task<Task> taskTask = Task.Factory.StartNew(async () =>
{
workerThread = Thread.CurrentThread;
entries.Add(("A", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
await Task.Delay(500);
entries.Add(("D", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
}, default, TaskCreationOptions.LongRunning, TaskScheduler.Default);
taskTask.Wait();
entries.Add(("B", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
workerThread.Join();
entries.Add(("C", stopwatch.ElapsedMilliseconds, workerThread.ThreadState));
await taskTask.Unwrap();
entries.Add(("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 毫秒很可能用于 inter-thread 通信,以及线程的创建和销毁。