等待 Task.Delay() vs. Task.Delay().Wait()

81
在C#中,我有以下两个简单示例:
[Test]
public void TestWait()
{
    var t = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Start");
        Task.Delay(5000).Wait();
        Console.WriteLine("Done");
    });
    t.Wait();
    Console.WriteLine("All done");
}

[Test]
public void TestAwait()
{
    var t = Task.Factory.StartNew(async () =>
    {
        Console.WriteLine("Start");
        await Task.Delay(5000);
        Console.WriteLine("Done");
    });
    t.Wait();
    Console.WriteLine("All done");
}

第一个示例创建了一个任务,该任务打印“Start”,等待5秒钟打印“Done”,然后结束任务。我等待任务完成,然后打印“All done”。当我运行测试时,它按预期执行。
第二个测试应具有相同的行为,但由于使用了async和await,因此任务内部的等待不会阻塞。但是,此测试仅打印“Start”,然后立即打印“All done”,并且“Done”永远不会被打印。
我不知道我为什么会得到这种行为:S非常感谢您的任何帮助 :)

1
Task.Delay 是非阻塞的。我看不出你为什么要使用第二种结构。 - Roy Dictus
2
@RoyDictus 他们都有自己的问题。你永远不应该调用Task.Wait() - Gusdor
1
在Main()方法中,您不能使用 "await"。 您必须使用Wait()或旧的Thread.Sleep()。 - Ron Inbar
1个回答

92

第二个测试有两个嵌套的任务,你正在等待最外层的任务完成,为了解决这个问题,你必须使用 t.Result.Wait()t.Result 获取内部任务。

第二种方法大致相当于这个:

public void TestAwait()
{
  var t = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Start");
                return Task.Factory.StartNew(() =>
                {
                    Task.Delay(5000).Wait(); Console.WriteLine("Done");
                });
            });
            t.Wait();
            Console.WriteLine("All done");
}

当您调用t.Wait()时,您正在等待立即返回的最外层任务。


处理此场景的“正确”方法是放弃使用Wait,而只使用await。一旦将UI附加到异步代码中,Wait可能会导致死锁问题

    [Test]
    public async Task TestCorrect() //note the return type of Task. This is required to get the async test 'waitable' by the framework
    {
        await Task.Factory.StartNew(async () =>
        {
            Console.WriteLine("Start");
            await Task.Delay(5000);
            Console.WriteLine("Done");
        }).Unwrap(); //Note the call to Unwrap. This automatically attempts to find the most Inner `Task` in the return type.
        Console.WriteLine("All done");
    }

更好的做法是使用Task.Run启动异步操作:

    [TestMethod]
    public async Task TestCorrect()
    {
        await Task.Run(async () => //Task.Run automatically unwraps nested Task types!
        {
            Console.WriteLine("Start");
            await Task.Delay(5000);
            Console.WriteLine("Done");
        });
        Console.WriteLine("All done");
    }

4
谢谢,能用了 :) 顺便问一下,总是可以使用Task.Run而不是Task.Factory.StartNew吗?还是有一些情况需要Task.Factory.StartNew,Task.Run无法处理? :) - svenskmand
2
Task.Run(someAction); 是一个方便的包装器,相当于 Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);。 - Daniel Leiszen
1
@DanielLeiszen:Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);不会展开嵌套的Task类型,因此它与Task.Run(someAction);并不完全等价。 - Arkane

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