Reputation: 196
I'm trying to have 2 tasks run one after the other, but I don't want to wait for them to finish since I'll be doing that later.
Initially I had an implementation with ContinueWith
but the double await
was bothering me and I don't see Unwrap()
as a significant improvement.
I decided to do a PoC to create the 2 tasks separately and have another task dispatched to manage them while I return the 2 initial tasks but weirdly the tasks become marked as completed as soon as I hit the await Task.Delay()
which basically means that in reality they're running concurrently.
This is the code:
var task1 = new Task(async ()=>
{
Console.WriteLine("Task1 Start");
await Task.Delay(5000);
Console.WriteLine("Task1 STOP");
});
var task2 = new Task(async () =>
{
Console.WriteLine("Task2 Start");
await Task.Delay(5000);
Console.WriteLine("Task2 STOP");
});
var taskParent = Task.Run(async () =>
{
Console.WriteLine("starting 1");
task1.Start();
await task1;
Console.WriteLine("starting 2");
task2.Start();
await task2;
});
Console.WriteLine("BEGIN await parent");
await taskParent;
Console.WriteLine("END await parent");
and the output is
BEGIN await parent
starting 1
Task1 Start
starting 2
Task2 Start
END await parent
Task2 STOP
Task1 STOP
So I go from my desire to have task2 begin after task1
to it finishing before task1
does. I can't see a reason why calling await Task.Delay
would mark the tasks as complete. Am I missing something?
EDIT To simplify my requirements since there seems to be a bit of confusion. The tasks must be returned before they are awaited, and the second task must run after the first. Some other thread will want the result later on and if it is completed fine if not it will await. I need to be able to await the second task and expect that the first one is executed before the second one.
Upvotes: 2
Views: 1567
Reputation: 43996
You'll hear frequently that using the Task
constructor is not recommended, and it's a wise advice. It's easier to create a Func<Task>
, and invoke it when you want to start the task, and it's also safer. The Task
constructor has the same hidden gotchas with the ContinueWith
method: it doesn't understand async delegates, and it requires to specify explicitly the scheduler when you start it. But if you know positively that the Task
constructor is the best tool for your problem, here is how you can use it:
Task<Task> taskTask1 = new Task<Task>(async () =>
{
Console.WriteLine("Task1 Start");
await Task.Delay(5000);
Console.WriteLine("Task1 STOP");
});
Task<Task> taskTask2 = new Task<Task>(async () =>
{
Console.WriteLine("Task2 Start");
await Task.Delay(5000);
Console.WriteLine("Task2 STOP");
});
Task taskParent = Task.Run(async () =>
{
Console.WriteLine("starting 1");
taskTask1.Start(TaskScheduler.Default);
await taskTask1.Unwrap();
Console.WriteLine("starting 2");
taskTask2.Start(TaskScheduler.Default);
await taskTask2.Unwrap();
});
Console.WriteLine("BEGIN await parent");
await taskParent;
Console.WriteLine("END await parent");
Output:
BEGIN await parent
starting 1
Task1 Start
Task1 STOP
starting 2
Task2 Start
Task2 STOP
END await parent
Notice that you don't need to Unwrap
the Task.Run
, because this method understands async delegates, and does the unwrapping automatically for you.
Notice also the TaskScheduler.Default
passed as argument to the Start
method. Not specifying the scheduler makes your code depended on the ambient TaskScheduler.Current
, and might generate warnings in the presence of the CA2008 analyzer.
Upvotes: 2
Reputation: 131729
Tasks aren't threads. There's never a good reason to create a task through its constructor and try to "start" it later. A Task is a Promise that something will complete in the future and may not even be executable. For example, Task.Delay
uses a timer to signal a TaskCompletionSource.
It's impossible to control execution through Start
, again because tasks arent' threads. Start
only schedules a task for execution, it doesn't actually run it. There's no guarantee the tasks will run in the order they were scheduled.
await
doesn't execute a task either, it awaits an already active task to complete, without blocking the calling thread. You don't need to await a task to make it execute. You only need to await it when you want to get its results, or wait for it to finish.
As for the question itself, it's unclear what the problem is. If the question is how to execute some async functions in sequence without awaiting the entire sequence, the easiest way would be to put them in their own async method, store its task in a variable and await it when needed :
async Task GetAnImageAsync()
{
//Load a URL from DB using Dapper
var url=await connection.QueryFirstOrDefault<string>("select top 1 URLs from Pictures");
//Get the image
var image=await client.GetByteArrayAsync(url);
//Save it
await File.WriteAllBytesAsync("blah.jpg",image);
}
...
async Task DoSomethingElse()
{
var imageTask=GetAnImageAsync();
//Do some other work
...
//Only await at the end
await imageTask();
}
Upvotes: 3
Reputation: 2933
Given that all task constructors accept Action
or variants of thereof, doing
var task1 = new Task(async ()=>
{
Console.WriteLine("Task1 Start");
await Task.Delay(5000);
Console.WriteLine("Task1 STOP");
});
task1.Start();
await task1;
is not dissimilar to doing
void EntryPoint()
{
CallAsync();
}
async void CallAsync()
{
Console.WriteLine("Task1 Start");
await Task.Delay(5000);
Console.WriteLine("Task1 STOP");
}
The CallAsync
from above will run until first suspension (await Task.Delay(5000)
) upon which it registers a Timer
callback and returns to the caller which promptly returns, completely unaware of any async
semantics.
If the application is still alive by the time the Timer
under Task.Delay
fires, it will run the continuation based on current TaskScheduler
and SynchronizationContext
as usual, writing Task1 STOP
to the console.
If you need to run tasks sequentially without awaiting them, either use ContinueWith
or implement a custom TaskScheduler
to do this for you.
Or even better, as suggested by @Panagiotis Kanavos, create a stand-alone async method which runs the sequence of tasks and await its result:
async Task PerformThingsAsync()
{
var task = RunMyTasksAsync();
// Do things
await task;
}
async Task RunTasksAsync()
{
await RunFistTaskAsync();
await RunSecondTaskAsync();
// ...
}
Upvotes: 0
Reputation: 101
Basically in c# a task created is always running, therefore if you create task, and not awaiting it, then it will be running in separate thread most likely. Take a look at: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
To archive sequential execution you either have to do a kind of scheduler for them or implement sequence on some sort of locking mechanism.
Continue with is the easiest method to guarantee that.
Upvotes: 0