Home > Mobile >  create multiple tasks, don't await them, but have them run sequentially
create multiple tasks, don't await them, but have them run sequentially

Time:04-11

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.

  • Related