Reputation: 12464
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
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 intoTask.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 controlTaskCreationOptions
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
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