Task.Wait不等待异步方法完成

3

以下是代码:

static async Task Main(string[] args)
{
    var t = new Task(async () => await AsyncTest());
    t.Start();
    t.Wait();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

我的期望是t.Wait()将会真正等待AsyncTest方法完成,输出结果应该是:

Method finished 
Main finished

实际上,输出只有 Main finished。当您在 AsyncTest 中输入 await Task.Delay(2000) 时,Wait() 就已经完成了。如果您用 await t / await Task.WhenAll(t) / Task.WaitAll(t) 替换 t.Wait(),情况也是一样的。

解决方法是将方法重写为同步实现或直接在 AsyncTest() 上调用 Wait()。但问题是为什么它会以这种奇怪的方式工作?

P.S. 这是代码的简化版本。我尝试实现延迟任务执行。实际上,Task 对象由程序的一部分创建,然后在满足某些特定条件之后由另一部分执行。

更新:将 t = new Task(async () => await AsyncTest()) 改写为 t = new Task(()=> AsyncTest().Wait()) 也可以解决问题。虽然我仍然不太理解为什么 Task 在委托内部使用 async/await 时不能正常工作。


2
不要使用 var task = new Task(...); task.Start();,而是直接使用 Task.Run - mtkachenko
1
在我的真实程序中,任务具有延迟执行。任务对象在一个地方创建,然后在程序的另一部分执行特定条件之后才被执行。 - Ghost Master
2
@WiktorZychla AsyncTest 返回一个任务,但是 async () => await AsyncTest() lambda 会将其丢弃,因为它本身是一个 Action (void)。() => AsyncTest().Wait() 可以“修复”它(仍然不正确,因为有 Wait())。 - GSerg
2
var t1 = new Task<Task>(async () => await AsyncTest()); var t2 = t1.Unwrap(); t1.Start(); t2.Wait(); - user4003407
显示剩余9条评论
4个回答

10
我仍然不太明白为什么在委托内部使用async/await时Task无法正常工作。
因为Task构造函数仅用于创建Delegate Tasks,即表示要运行同步代码的任务。由于代码是同步的,您的异步lambda被视为异步void lambda,这意味着Task实例不会异步等待AsyncTest完成。
更重要的是,在任何代码中,Task构造函数都不应该被使用,它没有任何有效的用例
Task.Task的一个很好的替代品是Task.Run,它理解async lambda。

在我的实际程序中,任务具有延迟执行。任务对象在一个地方创建,然后在特定条件之后由程序的另一部分执行。

在这种情况下,使用异步委托。具体来说,使用Func<Task>

static async Task Main(string[] args)
{
    Func<Task> func = AsyncTest;

    // Later, when we're ready to run.
    await func();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

1

来自@JonSkeet的引用

你的异步方法只返回void,这意味着没有简单的方法等待它完成。(你几乎总是应该避免使用异步void方法。它们真正只适用于订阅事件。)

因此,请查看您代码中的这一行:

var t = new Task(async () => await AsyncTest());

看一下Task构造函数的签名:

    public Task(Action action);
    public Task(Action action, CancellationToken cancellationToken);       
    public Task(Action action, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state);
    public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state, CancellationToken cancellationToken);
    public Task(Action<object> action, object state, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions);

它们都是Actions,并且您知道Action具有void返回类型。

static async Task Main(string[] args)
{
    // best way to do it
    await AsyncTest();


    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    // Don't use thread sleep, await task delay is fine
    // Thread.Sleep(2000);

    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

1
虽然这个回答有道理,但你似乎是从Jon的回答中复制了解释。 - Wiktor Zychla
@WiktorZychla 是的,第一段是来自传奇人物JonSkeet的,我最近编辑了答案,但是忘记发送更改并离开了笔记本电脑。 - Ali Bahrami

-2

检查一下

public static async Task Main(string[] args)
{
    var t = Task.Factory.StartNew(async () => await AsyncTest());
    //t.Start();
    t.Result.Wait();    
    t.Wait();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    //Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

还有这个链接


问题是关于:为什么模式 var t = new Task(...); t.Wait(); 不能按预期工作。 - mtkachenko

-4

不要使用 var task = new Task(...); task.Start();,而是直接使用 Task.Run

在第一种情况下,使用了构造函数 Task(Action action),因此实际上并没有等待异步方法。如果您检查没有异步等待语法糖的“真正”的 C#,您将看到 AsyncVoidMethodBuilder。在使用 Task.Run 的情况下,您使用 Task.Run(Func<Task> func),因此您会收到“真正”的任务。

因此,如果您想使用构造函数,则应该使用评论中的方法:new Task<Task>


4
这个回答怎么解决这个问题? - rs232

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接