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 awaits 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.

comments powered by Disqus

Related