You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Task.Run与Task.Factory.StartNew在await调用时行为不同?

Task.Run与Task.Factory.StartNew的行为差异解析

我原本以为Task.Run是Task.Factory.StartNew的更新且推荐的替代方案,二者行为一致,但在以下代码中发现了行为差异。我创建了一个简单的.NET 6控制台应用:

Worker.cs 代码

internal class Worker
{
    private readonly HttpClient httpClient = new();

    public async Task StartTaskRun(CancellationToken token)
    {
        await Task.Run(async () => await SomeWork(token));
    }

    public async Task StartTaskFactory(CancellationToken token)
    {
        await Task.Factory.StartNew(async () => await SomeWork(token));
    }

    private async Task SomeWork(CancellationToken token)
    {
        try
        {
            while (true)
            {
                var result = await httpClient.GetStringAsync("https://www.yahoo.com", token);
                await Task.Delay(5000, token);
            }
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Task has been canceled");
        }
    }
}

Program.cs 代码

var cancellationTokenSource = new CancellationTokenSource();

var worker = new Worker();

//worker.StartTaskFactory(cancellationTokenSource.Token); // 1# 返回
//worker.StartTaskRun(cancellationTokenSource.Token); // 2# 返回

//await worker.StartTaskFactory(cancellationTokenSource.Token); // 3# 返回
//await worker.StartTaskRun(cancellationTokenSource.Token); // #4 不返回

Console.WriteLine("Hello world");
Console.ReadLine();

cancellationTokenSource.Cancel();

Console.WriteLine("Cancelling");
Console.ReadLine();

在Program.cs中,四种调用启动方法的方式里:1#和2#是“即发即弃”调用,控制台正常输出“Hello World”并继续执行逻辑;但3#和4#结果不同:3#与1#、2#一样正常返回,4#却似乎一直在等待StartTaskRun()调用。


差异原因解析

核心差异在于Task.Factory.StartNew不会自动解包异步lambda的嵌套Task,而Task.Run会:

  1. Task.Factory.StartNew的行为
    当你调用Task.Factory.StartNew(async () => await SomeWork(token))时,传入的异步lambda本质是Func<Task>类型,因此StartNew返回的是Task<Task>——外层Task代表“启动lambda执行”这个操作,内层Task才是SomeWork的异步任务。
    当你await这个Task<Task>时,只会等待外层Task完成:外层Task在lambda执行到第一个await(httpClient.GetStringAsync)时就会完成,因为此时lambda会返回一个未完成的内层Task,外层任务的使命已经完成,所以await会立即结束,代码继续执行Console.WriteLine("Hello world")。

  2. Task.Run的行为
    Task.Run专门提供了针对Func<Task>的重载,它会自动解包嵌套的Task,直接返回lambda内部的SomeWork任务。
    因此await Task.Run(...)实际上是在等待整个SomeWork的异步流程完成,而SomeWork是一个无限循环(直到取消令牌触发),所以await会一直阻塞,直到调用cancellationTokenSource.Cancel()才会结束。

如果要让Task.Factory.StartNew达到和Task.Run一致的等待效果,需要手动调用Unwrap()方法解包:

await Task.Factory.StartNew(async () => await SomeWork(token)).Unwrap();

内容的提问来源于stack exchange,提问作者millie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 21:50:38