Damn Vegetables
Damn Vegetables

Reputation: 12464

Why doesn't it work to use ContinueWith to run task sequentially?

My goal was to start "Task2" after "Task1". At first I wrote code like "Code1" below, but it did not work (Task2 started before Task1 was finished). So I searched Stackoverflow and modified my code like "Code2" below as the existing answer suggested. I wonder why "Code1" did not work.

Code1

    static void Main(string[] args)
    {
        var p = new Program();
        p.Test2();
        Console.ReadKey();
    }

    void Test2()
    {
        Task.Factory.StartNew(async () =>
        {
            await Task1();
        }).ContinueWith((t) => {
            Task2();
        });
    }

    async Task Task1()
    {
        Debug.WriteLine("Task 1 starting....");
        await LongTask();
        Debug.WriteLine("Task 1 done");
    }

    Task LongTask()
    {
        return Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
        });            
    }

    void Task2()
    {
        Debug.WriteLine("Task 2");
    }

Code2

        Task.Factory.StartNew(async () =>
        {
            await Task1();
            Task2();
        }).ContinueWith((t) => {
            //Task2();
        });

Upvotes: 1

Views: 1010

Answers (2)

Farhad Jabiyev
Farhad Jabiyev

Reputation: 26635

Because when you are running task like Task.Factory.StartNew(async () => ... it returns Task<Task> (task of task). And first task terminates without waiting for the inner Task.

For preventing this situation, you can use Unwrap method:

Task.Factory.StartNew(async () =>
{
    await Task1();
})
.Unwrap()
.ContinueWith((t) => {
    Task2();
});

That StartNew will return Task<Task>, and it seems you want to execute continuation after inner task completion. That's why we need Unwrap. It “unwraps” the inner task that’s returned as the result of the outer task. Calling Unwrap on a Task gives you back a new Task (which we often refer to as a proxy) which represents the eventual completion of the inner task. And then, we are adding continuation to the inner task.

But, as Task.Run will do that unwrapping automatically you can use Task.Run in that case:

Task.Run(async () =>
{
    await Task1();
})
.ContinueWith((t) => {
    Task2();
});

And that can be simplified to:

Task.Run(async () =>
{
    await Task1();
    Task2();
});
 

Detailed information about diferences between Task.Run and Task.Factory.StartNew:

Read more here from Stephen Toub who is an engineer in the .Net team. The reason of decision in case of Task.Run:

Because we expect it to be so common for folks to want to offload work to the ThreadPool, and for that work to use async/await, we decided to build this unwrapping functionality into Task.Run.

By the way, as Stephen recommended just try to use Task.Run in most cases, but this in no way obsoletes Task.Factory.StartNew, there is still some places in which Task.Factory.StartNew must be used:

Task.Factory.StartNew still has many important (albeit more advanced) uses. You get to control TaskCreationOptions for how the task behaves. You get to control the scheduler for where the task should be queued to and run. You get to use overloads that accept object state, which for performance-sensitive code paths can be used to avoid closures and the corresponding allocations. For the simple cases, though, Task.Run is your friend.

Upvotes: 4

Maxim Kosov
Maxim Kosov

Reputation: 1980

Task.Factory.StartNew don't understand async lambdas. For Task.Factory.StartNew your lambda is just function returning Task. It doesn't automatically await that task. Also, note that using Task.Factory.StarNew and ContinueWith is discouraged in modern C# as it hard to use correctly (please read, "StartNew is Dangerous" by Stephen Cleary). You should use Task.Factory.StarNew or ContinueWith only if you can't do without them.

Instead, you can just use async/await one more time:

async Task Test2()
{
    await Task1();
    Task2();
}

or Task.Run:

async Task Test2()
{
    return Task.Run(await () => 
    {
        await Task1();
        Task2();
    });
}

The second approach might be convenient if you want to make sure that your asynchronous Task1() is started on background thread.

Upvotes: 2

Related Questions