简体   繁体   中英

await on task to finish, with operations after the 'await'

I know I may be downvoted but apparently I just don't understand async-await enough and the questions/answer I found, and the articles I found, didn't help me to find an answer for this question:

How can I make "2" to be printed out? Or actually, WHY doesn't 2 gets printed out, both in await t and in t.Wait()?:

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;
}

Edit: I'm creating a task and returning it cause in real-life I need to await on this task later on, from a different method.

Edit2: await Task.Delay is just a placeholder for a real-life await on a different function.

Printing '2'

The 2 is actually printed, 10 seconds after 1 is printed. You can observe this if you add Console.ReadLine();after printing 'Finished'.

The output is

Hello World
1
Finished
2

What is happening?

When you await t (which is res in GenerateTask method) you are awaiting the created Task and not the task that res created.

How to fix (fancy way)

You will need to await both the outer task and inner task. To be able to await the inner task you need to expose it. To expose it you need to change the type of the task from Task to Task<Task> and the return type from Task to Task<Task> .

It could look something like this:

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;
}

The output now is:

Hello World
1
2
Finished

How to fix (easy way)

The outer task is not needed.

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"); 
}

It looks like it's because the constructor to new Task only takes some form of an Action (So the Task never gets returned even though it's async). So essentially what you're doing is an Async void with your delegate. Your await Task.Delay(10000) is returning and the action is considered 'done'.

You can see this if you change the await Task.Delay(10000) to Task.Delay(10000).Wait() and remove the async from the delegate.

On another note though, I've never personally seen or used new Task before. Task.Run() is a much more standard way to do it, and it'll allow for the await to be used. Also means you don't have to call Start() yourself.

Also you might already know this but, in this specific case you don't need a new task at all. You can just do this:

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

Regarding your edits

Replacing your GenerateTask with what I wrote should do what you want. The async/await will turn your method into a Task that has started execution. This is exactly what you are trying to do so I'm not quite sure what you are asking with your edits.

The task returned from GenerateTask can be awaited whenever you want, or not awaited at all. You should almost never need to do new Task() . The only reason I can think is if you wanted to delay execution of the task until later, but there would be better ways around it rather than calling new Task() .

If you use the way I showed in your real-life situation, let me know what doesn't work about it and I'll be happy to help.

You should use Task.Run() rather than creating a Task directly:

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

Task.Start() doesn't work because it doesn't understand async delegates, and the returned task just represents the beginning of the task.

Note that you can't fix this by using Task.Factory.StartNew() either, for the same reason.

See Stephen Cleary's blog post on this issue, from which I quote:

[Task.Factory.StartNew()] Does not understand async delegates. This is actually the same as point 1 in the reasons why you would want to use StartNew. The problem is that when you pass an async delegate to StartNew, it's natural to assume that the returned task represents that delegate. However, since StartNew does not understand async delegates, what that task actually represents is just the beginning of that delegate. This is one of the first pitfalls that coders encounter when using StartNew in async code.

These comments also apply to the Task constructor, which also doesn't understand async delegates.

However, it's important to note that if you are already awaiting in the code and you don't need to parallelise some compute-bound code, you don't need to create a new task at all - just using the code in your Task.Run() on its own will do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM