等待任务完成,在“await”之后执行操作

4
我知道这个可能会被downvote,但显然我还不够理解async-await,我找到的问题/答案和文章并没有帮助我找到这个问题的答案:
我如何让"2"被打印出来?或者实际上,为什么在等待t和t.Wait()时都没有打印出2?
static Task t;
public static async void Main()
{
    Console.WriteLine("Hello World");
    t = GenerateTask();     
    await t;
    //t.Wait();
    Console.WriteLine("Finished");
}

public static Task GenerateTask()
{
    var res = new Task(async () => 
    {
        Console.WriteLine("1");
        await Task.Delay(10000);
        Console.WriteLine("2"); 
    });
    res.Start();
    return res;
}

编辑:我正在创建一个任务并返回它,因为在现实生活中,我需要在稍后的不同方法中等待此任务。

编辑2:await Task.Delay只是一个占位符,用于在不同的函数上实际等待。


您可能需要检查 Main 入口点的有效签名,以避免 避免 async void - Theodor Zoulias
相关内容:C#异步任务在完成前就已经结束 - Theodor Zoulias
2
不要使用Task构造函数(https://blog.stephencleary.com/2014/05/a-tour-of-task-part-1-constructors.html)。 - Stephen Cleary
3个回答

6

打印 '2'

实际上在打印 'Finished' 10秒后才会打印出数字 2。如果在打印 'Finished' 后添加 Console.ReadLine();,您就可以观察到这一点。

输出结果为

Hello World
1
Finished
2

发生了什么?

当您等待t(在GenerateTask方法中是res)时,您正在等待已创建的任务而不是res创建的任务。

如何修复(高级方法)

您需要等待外部任务和内部任务。为了能够等待内部任务,您需要公开该任务。要公开它,您需要将任务类型从Task更改为Task<Task>,并将返回类型从Task更改为Task<Task>

可能看起来像这样:

public static async Task Main()
{
    Console.WriteLine("Hello World");
    var outerTask = GenerateTask();     
    var innerTask = await outerTask; // what you have 
    await innerTask;                 // extra await
    Console.WriteLine("Finished");
    Console.ReadLine();
}

public static Task<Task> GenerateTask() // returns Task<Task>, not Task
{
    var res = new Task<Task>(async () => // creates Task<Task>, not Task
    {
        Console.WriteLine("1");
        await Task.Delay(TimeSpan.FromSeconds(10));
        Console.WriteLine("2"); 
    });
    res.Start();
    return res;
}

现在的输出为:

Hello World
1
2
Finished

如何轻松修复

不需要外部任务。

public static async Task Main()
{
    Console.WriteLine("Hello World");
    var t  = GenerateTask();     
    await t;
    Console.WriteLine("Finished");
    Console.ReadLine();
}

public static async Task GenerateTask()
{
    Console.WriteLine("1");
    await Task.Delay(TimeSpan.FromSeconds(10));
    Console.WriteLine("2"); 
}

4
看起来问题在于 new Task 的构造函数只接受 Action 形式的参数(所以尽管是异步的,但无法返回 Task)。因此本质上你正在使用带有委托的 Async void。你的 await Task.Delay(10000) 已经返回并且被认为“完成”。
如果将 await Task.Delay(10000) 更改为 Task.Delay(10000).Wait() 并从委托中删除 async,则可以看到这一点。
另外,我个人从未见过或使用过 new Task。 Task.Run() 是更标准的方法,它可以使 await 可用。这也意味着您不必自己调用 Start()。
此外,您可能已经知道,在这种特定情况下,您根本不需要新任务。您可以这样做:
    public static async Task GenerateTask()
    {
        Console.WriteLine("1");
        await Task.Delay(10000);
        Console.WriteLine("2");
    }

关于你的编辑

使用我所写的内容替换你的GenerateTask应该可以实现你想要的效果。使用async/await将使你的方法变为一个已经开始执行的Task,这正是你试图要做的,所以我不太确定你在编辑中要求什么。

GenerateTask返回的Task可以在任何时候等待,也可以完全不等待。你几乎永远不需要用new Task()。我唯一能想到的原因是如果你想要延迟任务的执行,但是有更好的方法来解决这个问题而不是调用new Task()

如果你在真实情况下使用我所展示的方式,让我知道哪里出了问题,我会乐意帮助你。


谢谢,我对我的问题进行了一些编辑。那么我应该如何修改我的代码,以便我等待某个函数(而不是Task.Delay),并运行一些其他代码,所有这些都包装在一个任务中,以便稍后可以等待它? - Alonzzo2
@Alonzzo2 我更新了我的回复,并针对您的编辑做出了回应。我认为我还没有完全理解您的用例,如果我没有回答好,可能需要更多细节。 - Lolop
你说得对,你和@tymtam建议的方法是可行的,而且这实际上是我尝试的第一件事。想象一下我的惊讶,它没有起作用:第一个原因是因为我在一个在线编译器(.net fiddle)中尝试它,它没有“等待”第二个输出的10秒钟。我没想到那会发生。第二个原因是我猜我的“现实生活”代码中还有另一个错误。 - Alonzzo2

3

你应该使用 Task.Run() 而不是直接创建一个 Task

public static Task GenerateTask()
{
    return Task.Run(async () =>
    {
        Console.WriteLine("1");
        await Task.Delay(10000);
        Console.WriteLine("2");
    });
}

因为它不理解异步委托,所以Task.Start()无法正常工作,返回的任务只代表任务的开始。
需要注意的是,由于同样的原因,您也不能通过使用Task.Factory.StartNew()来修复此问题。
请参见Stephen Cleary的博客文章,其中我引用了以下内容:

[Task.Factory.StartNew()] 不理解异步委托。这与希望使用 StartNew 的原因中的第1点实际上是相同的。问题 在于,当您将一个异步委托传递给 StartNew 时,自然而然地会认为返回的任务代表该委托。但是,由于StartNew不理解异步委托,因此该任务实际上表示的是该委托的开始。这是编写异步代码时程序员遇到的第一个陷阱之一。

这些评论也适用于Task构造函数,该构造函数也不理解异步委托。
然而,需要注意的是,如果您已经在代码中使用了await并且不需要并行化某些计算密集型代码,则根本不需要创建新的任务-只需使用您Task.Run()中的代码即可。

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