rnofenko
rnofenko

Reputation: 9533

How to wait for later started task

In my code sample main thread doesn't wait when task2 is finished.

public async Task Run()
{
    Console.WriteLine("Main start");
    await getTask1();
    Console.WriteLine("Main 2");
    var task2 = getTask2();
    await Task.Delay(1);
    Console.WriteLine("Main 3");
    task2.Start();
    await task2;
    Console.WriteLine("Main end");
}

private async Task getTask1()
{
    Console.WriteLine("task1 start");
    await Task.Delay(100);
    Console.WriteLine("task1 end");
}

private Task getTask2()
{
    return new Task(async () =>
    {
        Console.WriteLine("task2 start");
        await Task.Delay(100);
        Console.WriteLine("task2 end");
    });
}

Result of execution this code is

Main start
task1 start
task1 end
Main 2
Main 3
task2 start
Main end
task2 end

How can I change code where 'Main end' will be at the end of list.

Upvotes: 2

Views: 226

Answers (4)

Servy
Servy

Reputation: 203825

In getTask2 you're starting a Task that just starts another Task. So when you await that Task it will continue executing as soon as it has finished starting the inner task, not when that inner task has finished.

In this case there's no reason at all for you to create a new task just to start a new task in the first place; you should just use the approach used in the first method.

Of course, if, for some reason, you do need to create a new Task just to start a new task then you need to get the inner Task out and ensure that the main method only continues executing when that inner Task has finished. There is an Unwrap method to create a Task that is logically equivalent to a Task that is the Result of another Task:

private Task getTask2()
{
    Task<Task> task = new Task<Task>(async () =>
    {
        Console.WriteLine("task2 start");
        await Task.Delay(100);
        Console.WriteLine("task2 end");
    });
    task.Start();
    return task.Unwrap();
}

But you don't even need to do that. Task.Run will automatically unwrap the value for you if it is given a lambda that produces a Task:

private Task getTask2()
{
    return Task.Run(async () =>
    {
        Console.WriteLine("task2 start");
        await Task.Delay(100);
        Console.WriteLine("task2 end");
    });
}

And once again, this is all assuming that just starting the asynchronous operation needs to be offloaded to another thread, which should be very rare. Usually this means that there's a bug in that asynchronous method in that it shouldn't be doing long running synchronous work before giving you its Task in the first place, but on occasion that method will be from an external library that you don't control, so this would be your only option.

Upvotes: 2

TheYoungGrasshopper
TheYoungGrasshopper

Reputation: 13

You have an error in your code. You have not marked the getTask2 method with the keyword async yet you await its result. Even though you are using an async delegate within the method you must use the async keyword if it is to be successfully awaited.

Here's an idea as to how you can accomplish your desired result.

public async Task Run()
    {
        Console.WriteLine("Main start");
        await getTask1();
        await getTask2();
        Console.WriteLine("Main end");
    }

    public async Task AlternateRun()
    {
        Console.WriteLine("Main start");
        List<Task> task_list = new List<Task>();
        task_list.Add(getTask1());
        task_list.Add(getTask2());

        var task_result = Task.WhenAll(task_list);
        task_result.Wait();
        Console.WriteLine("Main end");
    }

    private async Task getTask1()
    {
        Console.WriteLine("task1 start");
        await Task.Delay(100);
        Console.WriteLine("task1 end");
    }

    private async Task getTask2()
    {
        Console.WriteLine("task2 start");
        await Task.Delay(100);
        Console.WriteLine("task2 end");
    }

I have also added an AlternateRun method that will demonstrate an alternative style of awaiting multiple tasks. If you need your tasks to be completed in order then consider using the ContinueWith extension method.

Notice that I also removed your task2.Start()statement. This is already implied by the await keyword.

Hope this helps.

Upvotes: -1

Yacoub Massad
Yacoub Massad

Reputation: 27871

The constructor overload for Task that you are using has the following signature:

public Task(Action action)

Although you can assign the following expression to a variable of type Action:

async () =>
    {
        Console.WriteLine("task2 start");
        await Task.Delay(100);
        Console.WriteLine("task2 end");
    }

this action will point to a synchronous function that will synchronously finish once the await in await Task.Delay(100); is reached (although the rest of the "action" will continue asynchronously). So, as far as the Task object is concerned, the execution of this Action is completed once this await is reached. This means that the await in await task2; will asynchronously continue at that point which explains why "Main end" is printed before "task2 end".

So basically, the constructor for Task does not support asynchronous actions, just synchronous ones.

To fix this problem, you need to start a task using a method that understands asynchronous actions. An asynchronous action looks like this: Func<Task>.

The Task.Run method supports asynchronous actions because it has the following overload:

public static Task Run(Func<Task> function)

So to fix your code, change return new Task(... to return Task.Run(... and then don't call the Start method on the returned task since Task.Run will create a Task that is already started.

If you want to delay the execution of the second "task", then I suggest to make the getTask2 method return Func<Task> (an asynchronous action) instead of Task like this:

private Func<Task> getTask2()
{
    return async () =>
    {
        Console.WriteLine("task2 start");
        await Task.Delay(100);
        Console.WriteLine("task2 end");
    };
}

And then run such asynchronous action when you want from the Run method like this:

public async Task Run()
{
    Console.WriteLine("Main start");
    await getTask1();
    Console.WriteLine("Main 2");
    var task2 = getTask2(); //construct asynchronous action
    Console.WriteLine("Main 3");
    await Task.Run(task2); //execute the asynchronous action
    Console.WriteLine("Main end");
}

Upvotes: 1

Scott Chamberlain
Scott Chamberlain

Reputation: 127603

new Task( does not work with async functions by itself. You are creating a Task<Task>, you need to call Unwrap() before you await the inner task.

private Task<Task> getTask2()
{
    return new Task(async () =>
    {
        Console.WriteLine("task2 start");
        await Task.Delay(100);
        Console.WriteLine("task2 end");
    });
}

var task2 = getTask2();
Console.WriteLine("Main 3");
task2.Start();
await task2.Unwrap();

Upvotes: 0

Related Questions