Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

async-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.

like image 824
Alonzzo2 Avatar asked Dec 10 '22 23:12

Alonzzo2


2 Answers

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"); 
}
like image 110
tymtam Avatar answered Dec 26 '22 21:12

tymtam


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.

like image 20
Lolop Avatar answered Dec 26 '22 20:12

Lolop