Task.Factory.Startnew with Async Lambda and Task.Waitall

Task.Factory.StartNew with async lambda and Task.WaitAll

Task.Factory.StartNew doesn't recognise async delegates as there is no overload that accepts a function returning a Task.

This plus other reasons (see StartNew is dangerous) is why you should be using Task.Run here:

tasks.Add(Task.Run(async () => ...

Task Factory for each loop with await

This is a typical problem that C# 8.0 Async Streams are going to solve very soon.

Until C# 8.0 is released, you can use the AsyncEnumarator library:

using System.Collections.Async;

class Program
{
private async Task SQLBulkLoader() {

await indicators.file_list.ParallelForEachAsync(async fileListObj =>
{
...
await s.WriteToServerAsync(dataTableConversion);
...
},
maxDegreeOfParalellism: 3,
cancellationToken: default);
}

static void Main(string[] args)
{
Program worker = new Program();
worker.SQLBulkLoader().GetAwaiter().GetResult();
}
}

I do not recommend using Parallel.ForEach and Task.WhenAll as those functions are not designed for asynchronous streaming.

Correctly awaiting Task.Run with async lambda expression

Your code will wait till the end of the compute using the task mechanism. This will not block your thread but it will not execute code beyond this point until everything in your loop is done.

public static async Task Test()
{
await Task.Run(async () =>
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine($"inside {i}");
}
});
Console.WriteLine("Done");
}

will give the following output:

inside 0
inside 1
inside 2
inside 3
inside 4
inside 5
inside 6
inside 7
inside 8
inside 9
Done

How to wait until all the tasks finish before returning from method in C#

When all can be used to create a new task which completes when all tasks in it completes, you can then proceed fetching results out of that task using the regular async patterns.

https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.whenall(v=vs.110).aspx

So
//If your validation results are strings:
Task[] tasks = ...
var things = await Task.WhenAll(tasks);

Alternatively WaitAll can be used, but if you need to do work on the output that is an awkward aproach here. IMO staying in the async context is always better anyways.

Task.WaitAll(tasks);
foreach(var t in tasks) DoSomething(t.Result);

https://msdn.microsoft.com/en-us/library/dd270695(v=vs.110).aspx

Besides, it blocks the current thread AFAIK.


A more complete console example:
https://dotnetfiddle.net/JMLHxR

using System;
using System.Threading.Tasks;

public class Program
{
private static int counter = 0;

public static void Main()
{
Random rnd = new Random();
var tasks = new Task<object>[]
{
// If your target method returns a task.
// - note: A Proxy or similar approach will probably be more readable.
CreateTask(rnd.Next(500,2000)).ContinueWith(t => (object)t.Result),
CreateOtherTask(rnd.Next(500,2000)).ContinueWith(t => (object)t.Result),
CreateTask(rnd.Next(500,2000)).ContinueWith(t => (object)t.Result),
CreateOtherTask(rnd.Next(500,2000)).ContinueWith(t => (object)t.Result),

//If your target method is syncronious.
Task.Run(() => (object)CreateSimple()),
Task.Run(() => (object)CreateMessage()),
Task.Run(() => (object)CreateSimple()),
Task.Run(() => (object)CreateMessage())
};

Task.WaitAll(tasks);
foreach(var t in tasks)
Console.WriteLine(t.Result);

// They are already completed here, but just to show the syntax.
// .Result should obvisously be awaited instead.
var all = Task.WhenAll(tasks).Result;
foreach(var result in all)
Console.WriteLine(result);
}

private static string CreateSimple()
{
int id = Program.counter++;
return "Task [" + id + "] delayed: NONE";
}

private static Message CreateMessage()
{
return new Message(CreateSimple());
}

private static async Task<string> CreateTask(int delay)
{
int id = Program.counter++;
await Task.Delay(delay);
return "Task [" + id + "] delayed: " + delay;
}

private static async Task<Message> CreateOtherTask(int delay)
{
int id = Program.counter++;
await Task.Delay(delay);
return new Message("Task [" + id + "] delayed: " + delay);
}

public class Message {
private string message;

public Message(string msg) { message = msg; }

public override string ToString(){ return message; }
}
}

Task.WaitAll not waiting for task to complete

You should avoid using Task.Factory.StartNew with async-await. You should use Task.Run instead.

An async method returns a Task<T>, an async delegate does as well. Task.Factory.StartNew also returns a Task<T>, where its result is the result of the delegate parameter. So when used together it returns a Task<Task<T>>>.

All that this Task<Task<T>> does is execute the delegate until there's a task to return, which is when the first await is reached. If you only wait for that task to complete you aren't waiting for the whole method, just the part before the first await.

You can fix that by using Task.Unwrap which creates a Task<T> that represents that Task<Task<T>>>:

Task<Task> wrapperTask = Task.Factory.StartNew(...);
Task actualTask = wrapperTask.Unwrap();
Task.WaitAll(actualTask);

Task.Factory.StartNew with async lambda and Task.WaitAll

Task.Factory.StartNew doesn't recognise async delegates as there is no overload that accepts a function returning a Task.

This plus other reasons (see StartNew is dangerous) is why you should be using Task.Run here:

tasks.Add(Task.Run(async () => ...

How to wait on Async tasks

So there are several separate bugs here.

First, for Execute, you're using StartNew with an async lambda. Since StartNew doesn't have a Task<Task> returning overload, like Task.Run does, you've got a method that returns a Task indicating when the asynchronous operation has finished starting, not when the asynchronous operation has finished, which means that the Task returned by Execute will be completed basically right away, rather than after Delay finishes or the action you call finishes. Additionally, there's simply no reason to use StartNew or Run at all when running asynchronous methods, you can just execute them normally and await them without pushing them to a thread pool thread.

Next, Execute accepts an Action, which implies that it's a synchronous method that doesn't compute any value. What you're providing is an asynchronous method, but as the delegate doesn't return a Task, Execute can't await it. If you want Execute to handle asynchronous methods, it needs to accept a delegate that returns a Task.

So given all of that Execute should look like this.

private static async Task Execute(Func<Task> action)
{
await Task.Delay(TimeSpan.FromMilliseconds(5));
await action();
}

Next onto the Main method. As mentioned before Execute is accepting an Action when you're trying to provide an async method. This means that when the action is run the code will continued executing before your actions have finished. You need to adjust it to using a Task returning method.

After all of that, your code still has a race condition in it, at a conceptual level, that will prevent you from theoretically getting the results in the right order. You're performing 3 different operations in parallel, and as a result of that, they can finish in any order. While you are atomically incrementing the counter, it's possible for one thread to increment the counter, then another to run, increment the counter, print its value, then have the other thread run again and print out the value, given you a possible output of what you have, even after fixing all of the bugs mentioned above. To ensure that the values are printed in order, you need to ensure that the increment and the console write are performed atomically.

Now you can write out your Main method like so:

int ExecutionCounter = 0;
object key = new object();
Func<Task> myAction = async () =>
{
await Task.Delay(TimeSpan.FromMilliseconds(5));
lock (key)
{
Console.WriteLine(++ExecutionCounter);
}
};

var actions = new[] { myAction, myAction, myAction };

Task.WaitAll(actions.Select(a => Execute(a)).ToArray()); //This blocks, right?

And yes, as your comment mentions, calling WaitAll will block, rather than being asynchronous.

Generate ListTask with using index in for like parameter

There are a few issues here.

To directly answer your question, you need to create a closure over i to prevent it being updated before it is accessed in your async code.

You can do this by swapping your for loop with Enumerable.Range.

Another issue is that you are running GetValueAsync on the ThreadPool but not awaiting it. Subsequently, your Task.WaitAll will be waiting for the outer Tasks only, not the Tasks returned by GetValueAsync.

Here is an example of what you could do:

var tasks = Enumerable.Range(0, valueSize)
.Select(async i =>
{
string val = await _tcpRepository.GetValueAsync(i);
Values[i] = val.GetInt32();
});

Your final issue is the use of Task.WaitAll; this introduces the sync-over-async antipattern. You should allow async to grow through your code base, and instead use Task.WhenAll, which returns a Task that completes when all the provided Tasks are complete:

await Task.WhenAll(tasks);


Related Topics



Leave a reply



Submit