Romasz
Romasz

Reputation: 29792

Deferring tasks execution

I'm playing with Tasks and I would like to defer my task's execution.

I've a sample method like this:

private async Task<bool> DoSomething(string name, int delayInSeconds)
{
    Debug.WriteLine($"Inside task named: {name}");
    await Task.Delay(TimeSpan.FromSeconds(delayInSeconds));
    Debug.WriteLine($"Finishing task named: {name}");
    return true;
}

I would like to create few tasks first, then perform some job and after this run those tasks. As the line Task<bool> myTask = DoSomething("Name", 4); fires the task right away, I've figured something like this:

string[] taskNames = new string[2];

Task<Task<bool>>[] myTasks = new Task<Task<bool>>[2];
myTasks[0] =  new Task<Task<bool>>(async () => await DoSomething(taskNames[0], taskNames[0].Length));
myTasks[1] =  new Task<Task<bool>>(async () => await DoSomething(taskNames[1], taskNames[1].Length));

// I think I can declare it also like this, but this will create tasks later
//IEnumerable<Task<Task<bool>>> myTasks = taskNames.Select(x => new Task<Task<bool>>(async () => await DoSomething(x, x.Length)));

taskNames[0] = "First";
taskNames[1] = "Second";

Debug.WriteLine($"Tasks created");

var results = await Task.WhenAll(myTasks.Select(x => { x.Start(); return x.Unwrap(); }));
Debug.WriteLine($"Finishing: {results.Select(x => x.ToString()).Aggregate((a,b) => a + "," + b) }");

Can this be done different way, without wrapping task?

Upvotes: 1

Views: 2792

Answers (2)

Kirill Shlenskiy
Kirill Shlenskiy

Reputation: 9587

You can just use Task-producing delegates to simplify things a bit:

string[] taskNames = new string[2];

Func<Task<bool>>[] myTasks = new Func<Task<bool>>[2];
myTasks[0] = new Func<Task<bool>>(async () => await DoSomething(taskNames[0], taskNames[0].Length));
myTasks[1] = new Func<Task<bool>>(() => DoSomething(taskNames[1], taskNames[1].Length)); // Shorter version, near-identical functionally.

// I think I can declare it also like this, but this will create tasks later
//IEnumerable<Task<Task<bool>>> myTasks = taskNames.Select(x => new Task<Task<bool>>(async () => await DoSomething(x, x.Length)));

taskNames[0] = "First";
taskNames[1] = "Second";

Debug.WriteLine($"Tasks created");

var results = await Task.WhenAll(myTasks.Select(x => x()));
Debug.WriteLine($"Finishing: {results.Select(x => x.ToString()).Aggregate((a, b) => a + "," + b) }");

Caveat: DoSomething will execute synchronously up to the first await when you invoke those delegates, so the behaviour is similar, but not exactly identical.

Alternatively, your IEnumerable-based solution will work fine too. Just write an iterator method and yield return tasks as you start them.

Personally though I'd just do this:

string[] taskNames = new string[2];

taskNames[0] = "First";
taskNames[1] = "Second";

var results = await Task.WhenAll(taskNames.Select(n => DoSomething(n, n.Length)));
Debug.WriteLine($"Finishing: {results.Select(x => x.ToString()).Aggregate((a, b) => a + "," + b) }");   

Upvotes: 5

Daniel Mann
Daniel Mann

Reputation: 59020

You're not just "creating a Task" in your example. You're invoking a method, DoSomething, that returns a Task. Assuming these are async methods, the Task creation and starting takes place behind the scenes in compiler-generated code.

The solution to this problem is easy: Don't invoke the method until you're ready for the method to be running. Imagine how confusing the behavior you're asking for would be in any other context.

Upvotes: 1

Related Questions