Create a Task and Start it Later
I was working on something recently where I wanted to define a task, but not run it immediately. I couldn’t find much useful information about this, so I decided to write about it, once I figured it out.
In the end, it took quite a bit of time to get this working the way I wanted, so it should save time for the next person.
What I want to do
I want to create a task that will call a method, but not invoke the method when I create the task. I want to make the decision about starting the task later. Effectively, deferring the execution of the task.
Why? There are some interesting cases where adding tasks to a cache, list, or dictionary can be very helpful. I’ll blog about them soon, but I want to lay out the principle first.
The method to run
I have a simple method that takes an int, returns a string, and runs asynchronously.
async Task<string> DoWorkAsync(int number)
{
Console.WriteLine($"Running DoWorkAsync with a {number} second delay");
DateTime started = DateTime.Now;
await Task.Delay(number * 1000);
return $"Started at {started:HH:MM:ss}, finished at {DateTime.Now:HH:MM:ss}. This value took {number} seconds to generate";
}
Nothing complicated there, but representative of any method you might have because it accepts a parameter, returns something, and runs asynchronously. Things are a bit simpler when there is nothing returned and it is synchronous, so the example should be cover the easier and harder cases.
Creating the task
Here’s how I create the task, but I don’t invoke it.
Task<Task<string>> taskToRunLater = new Task<Task<string>>(() => DoWorkAsync(3));
Note how I have new Task<Task<string>>
. The outer Task
will return Task<string>
. This is because the method I want to run returns a Task<string>
.
Then I pass the constructor of the first Task
a Func
using a Lambda that will return the Task<string>
.
That’s the task setup.
If you run the application in a debugger and pause it after the above line, you will see that the task is not running yet.
Id = 1, Status = Created, Method = "System.Threading.Tasks.Task`1[System.String] <<Main>$>b__0_0()", Result = "{Not yet computed}"
See how the status is Created
and the result is Not yet computed
.
Running the task
Time to start the task.
taskToRunLater.Start();
That’s it. The task is now running.
Waiting for the result
This is where it gets a bit interesting.
I first need to await
the taskToRunLater
, this will return Task<string>
.
Then I need to await
that.
Task<string> taskString = await taskToRunLater; // first await
string returnedString = await taskString; // second await to get the string I want
Now I can use the string that was returned.
You don’t have to perform the await
s that way, they can be combined -
string returnedString = await await taskToRunLater;
Putting it all together
Here is the full source code -
1Task<Task<string>> taskToRunLater = new Task<Task<string>>(() => DoWorkAsync(3));
2Console.WriteLine("Task has been setup, now pausing for a second...");
3await Task.Delay(1000);
4
5Console.WriteLine("Starting the task...");
6taskToRunLater.Start();
7
8Task<string> taskString = await taskToRunLater;
9
10string returnedString = await taskString;
11
12Console.WriteLine(returnedString);
13
14async Task<string> DoWorkAsync(int number)
15{
16 Console.WriteLine($"Running DoWorkAsync with a {number} second delay");
17 DateTime started = DateTime.Now;
18 await Task.Delay(number * 1000);
19 return $"Started at {started:HH:MM:ss}, finished at {DateTime.Now:HH:MM:ss}. This value took {number} seconds to generate";
20}
21
22Console.WriteLine("await-ing taskToRunLater again...");
23Console.WriteLine(await await taskToRunLater);
The output
Run the application and you will see the following output -
Task has been setup, now pausing for a second...
Starting the task...
Running DoWorkAsync with a 3 second delay
Started at 19:03:07, finished at 19:03:10 This value took 3 seconds to generate
await-ing taskToRunLater again...
Started at 19:03:07, finished at 19:03:10 This value took 3 seconds to generate
Next steps
I’m going to use this approach with caching, where I want to store the task in a cache, and only run it the first time the value is requested.