I'm trying to have 2 task 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?
CodePudding user response:
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();
// ...
}
CodePudding user response:
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();
}
CodePudding user response:
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://docs.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.