Task.Factory.StartNew + TaskCreationOptions.LongRunning 说明

问题描述 投票:0回答:1

我正在尝试理解 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);
        }
    }
}
c# .net asynchronous task task-parallel-library
1个回答
2
投票

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

在 Fiddle 上尝试一下

工作线程的生命周期最多为6毫秒。它真正要做的就是实例化一个异步状态机,并使用

System.Threading.Timer
组件安排回调。对于如此微小的工作量来说,6 毫秒对我来说就像是一个亿万年。这 6 毫秒很可能用于线程间通信以及线程的创建和销毁。

© www.soinside.com 2019 - 2024. All rights reserved.