如何声明一个未启动的任务,将等待另一个任务?

Elo*_*Elo 9 .net c# task async-await .net-4.6

我已经完成了单元测试,但我不明白为什么“等待Task.Delay()”不等待!

   [TestMethod]
    public async Task SimpleTest()
    {
        bool isOK = false;
        Task myTask = new Task(async () =>
        {
            Console.WriteLine("Task.BeforeDelay");
            await Task.Delay(1000);
            Console.WriteLine("Task.AfterDelay");
            isOK = true;
            Console.WriteLine("Task.Ended");
        });
        Console.WriteLine("Main.BeforeStart");
        myTask.Start();
        Console.WriteLine("Main.AfterStart");
        await myTask;
        Console.WriteLine("Main.AfterAwait");
        Assert.IsTrue(isOK, "OK");
    }
Run Code Online (Sandbox Code Playgroud)

这是单元测试的输出:

单元测试输出

这怎么可能是“等待”不等待,而主线程继续呢?

nvo*_*igt 8

new Task(async () =>

一个任务不需要一个Func<Task>,而是一个Action。它将调用您的异步方法,并期望它在返回时结束。但事实并非如此。它返回一个任务。新任务不会等待该任务。对于新任务,一旦方法返回,作业就完成了。

您需要使用已经存在的任务,而不是将其包装在新任务中:

[TestMethod]
public async Task SimpleTest()
{
    bool isOK = false;

    Func<Task> asyncMethod = async () =>
    {
        Console.WriteLine("Task.BeforeDelay");
        await Task.Delay(1000);
        Console.WriteLine("Task.AfterDelay");
        isOK = true;
        Console.WriteLine("Task.Ended");
    };

    Console.WriteLine("Main.BeforeStart");
    Task myTask = asyncMethod();

    Console.WriteLine("Main.AfterStart");

    await myTask;
    Console.WriteLine("Main.AfterAwait");
    Assert.IsTrue(isOK, "OK");
}
Run Code Online (Sandbox Code Playgroud)

  • 也可以选择Task.Run(async()=&gt; ...) (4认同)

The*_*ias 5

问题是您正在使用非泛型Task类,这并不意味着产生结果。因此,当您创建Task传递异步委托的实例时:

Task myTask = new Task(async () =>
Run Code Online (Sandbox Code Playgroud)

...代表被视为async void。Anasync void不是 a ,它不能被等待,它的异常不能被处理,并且它是StackOverflow 和其他地方的沮丧的程序员提出的数千个问题Task的根源。解决方案是使用泛型类,因为你想返回一个结果,而结果是另一个。所以你必须创建一个:Task<TResult>TaskTask<Task>

Task<Task> myTask = new Task<Task>(async () =>
Run Code Online (Sandbox Code Playgroud)

现在,当你Start创建外部时Task<Task>,它几乎会立即完成,因为它的工作只是创建内部TaskTask然后你也必须等待内在。可以这样做:

myTask.Start(TaskScheduler.Default);
Task myInnerTask = await myTask;
await myInnerTask;
Run Code Online (Sandbox Code Playgroud)

你有两种选择。如果您不需要对内部的显式引用,Task那么您只需等待外部Task<Task>两次:

await await myTask;
Run Code Online (Sandbox Code Playgroud)

...或者您可以使用内置的扩展方法Unwrap,将外部任务和内部任务合并为一个:

await myTask.Unwrap();
Run Code Online (Sandbox Code Playgroud)

当您使用更流行的Task.Run创建热任务的方法时,这种展开会自动发生,因此Unwrap现在不经常使用。

如果您决定异步委托必须返回结果,例如 a string,那么您应该将该myTask变量声明为 类型Task<Task<string>>

注意:我不赞成使用Task构造函数来创建冷任务。作为一种做法,人们普遍不赞成,原因我不太清楚,但可能是因为它很少被使用,以至于它有可能让其他不知情的代码用户/维护者/审阅者感到惊讶。

一般建议:每次提供异步委托作为方法的参数时都要小心。理想情况下,此方法应该期望一个Func<Task>参数(意味着理解异步委托),或者至少一个Func<T>参数(意味着至少生成的Task不会被忽略)。在不幸的情况下,此方法接受Action,您的委托将被视为async void。这很少是您想要的(如果有的话)。