When does a C# Task actually start?
Asked Answered
T

2

45

When does a Task actually start?

public void DoSomething() {
    Task myTask = DoSomethingAsync();

    Task.WaitAll(new[] { myTask }, 2000);
}

public async Task DoSomethingAsync() {
    await SomethingElse();
}

Does it start immediately when initializing it in Task myTask = DoSomethingAsync(); or does it start when you say to wait for it in Task.WaitAll(new[] { myTask }, 2000); ?

Townsley answered 29/3, 2017 at 9:19 Comment(2)
This is a big question. Have a look here for starters: blog.stephencleary.com/2013/11/there-is-no-thread.htmlJuncture
Patrick Hofman already has given the correct answer. For your example: DoSomethingAsync() will immediatly run as far as possible - which means e.g. up to the first point where it awaits some inner task inside which has not yet finished. Only then it will return and the continuation will get attached. If DoSomethingAsync() has dozens of nested await functions inside, where all complete synchronously (e.g. the last one is a Task.FromResult()) then myTask will be completed before the control is given back to the caller.Ebony
M
66

Calling an async method returns a hot task, a task that has already been started. So there is no actual code necessary to force it to run.

According MSDN (thanks to Stephen Cleary) the Task-based Asynchronous Pattern (TAP) pattern requires returned tasks to be hot. That means that all tasks, except those created with new Task will be hot.

From the referenced article:

Tasks that are created by the public Task constructors are referred to as cold tasks... All other tasks begin their life cycle in a hot state.

Mangonel answered 29/3, 2017 at 9:23 Comment(4)
"When you call a method that returns a Task without async, it will not be started." sounds a bit confusing to me. Majority of methods that return task without async still return task that is already started, because they start it inside method itself.Erastes
@Erastes 'automatically' > better?Mangonel
The async modifier is used by the compiler to build the state machine. Other than the constructors of Task/Task<T>, any API I can think of returns an hot task. That's what Task.Run does. So, what do you mean by "When you call a method that returns a Task without async, it will not be started automatically."?Xerox
return new Task ... for example @PauloMorgadoMangonel
A
0

I would like to contribute with some examples even though Patrick Hofman gave the answer (especially by pointing to this section in the linked article https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap?redirectedfrom=MSDN#task-status).

Hopefully the examples can help with the interpretation.

If you start by just making some simple HOT tasks you would see they start as soon as they have been initialised.

// Using just some HOT task
var first = Task.Delay(2_500);
var second = Task.Delay(2_500);

await Task.Delay(2_500);

Console.WriteLine("Start awaiting HOT tasks");
var stopwatch = Stopwatch.StartNew();
await Task.WhenAll(first, second);
Console.WriteLine(stopwatch.Elapsed);

The above code snippet will output below which validates that the tasks has been running between the initialisation and await Task.WhenAll(…).

Start awaiting HOT tasks
00:00:00.0000866

Now if you instead used the Task constructor the task wouldn’t be started but be in state TaskStatus.Created. We need to start them explicitly as in below example:

// Using Task constructor
var firstNew = new Task(() => Task.Delay(2_500).GetAwaiter().GetResult());
var secondNew = new Task(() => Task.Delay(2_500).GetAwaiter().GetResult());

await Task.Delay(2_500);

Debug.Assert(firstNew.Status == TaskStatus.Created && secondNew.Status == TaskStatus.Created);
firstNew.Start();
secondNew.Start();
Console.WriteLine("Start awaiting NEW tasks");
stopwatch.Restart();
await Task.WhenAll(firstNew, secondNew);
Console.WriteLine(stopwatch.Elapsed);

above code gives output:

Start awaiting NEW tasks
00:00:02.5070535

If you would use either Task.Run or Task.Factory they would return started task. As a example this code:

// Run
var firstRun = Task.Run(async () => await Task.Delay(2_500));
var secondRun = Task.Run(async () => await Task.Delay(2_500));

await Task.Delay(2_500);

Console.WriteLine("Start awaiting RUN tasks");
stopwatch.Restart();
await Task.WhenAll(firstRun, secondRun);
Console.WriteLine(stopwatch.Elapsed);

// Factory 
var firstFactory = Task.Factory.StartNew(async () => await Task.Delay(2_500));
var secondFactory = Task.Factory.StartNew(async () => await Task.Delay(2_500));

await Task.Delay(2_500);

Console.WriteLine("Start awaiting FACTORY tasks");
stopwatch.Restart();
await Task.WhenAll(firstFactory, secondFactory);
Console.WriteLine(stopwatch.Elapsed);

outputs:

Start awaiting RUN tasks
00:00:00.0002207
Start awaiting FACTORY tasks
00:00:00.0005118
Aleedis answered 11/10, 2023 at 19:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.