I'm working with Tasks in C#. I'm having an issue with function invocation when I add my functions to the List. What happens is, instead of waiting until the Task.WhenAll(...) to invoke all functions at once, it invokes them immediately when added...only whenever I need to add them as a NameOfFunction() (so with parenthesis, with or without params).
This does not work and causes invocation immediately:
List<Task> rtcTasks = new List<Task>();
rtcTasks.Add(RunInitialProcess());
rtcTasks.Add(RunSecondaryProcess());
Task.WhenAll(rtcTasks).Wait();
This does work and invokes all when the process reaches Task.WhenAll(...);
List<Task> rtcTasks = new List<Task>();
rtcTasks.Add(Task.Run(RunInitialProcess));
rtcTasks.Add(Task.Run(RunSecondaryProcess));
Task.WhenAll(rtcTasks).Wait();
My issues is, I'd like to pass in functions that contain arguments that I can use for handling very easily without having to declare accessible objects in the current class I'm in.
Both functions are:
private async Task FunctionNameHere(){...}
CodePudding user response:
This does work and invokes all when the process reaches
Task.WhenAll(...);
.
Nope, it doesn't. The two asynchronous methods are invoked on ThreadPool
threads a few microseconds after you add the tasks in the list, not when the Task.WhenAll
method is invoked. Here is a minimal demonstration of this behavior:
Print("Before Task.Run");
Task task = Task.Run(() => DoWorkAsync());
Print("After Task.Run");
Thread.Sleep(1000);
Print("Before Task.WhenAll");
Task whenAllTask = Task.WhenAll(task);
Thread.Sleep(1000);
Print("Before await whenAllTask");
await whenAllTask;
Print("After await whenAllTask");
static async Task DoWorkAsync()
{
Print("--Starting work");
await Task.Delay(3000);
Print("--Finishing work");
}
static void Print(object value)
{
Console.WriteLine($@"{DateTime.Now:HH:mm:ss.fff} [{Thread.CurrentThread
.ManagedThreadId}] > {value}");
}
Output:
20:25:50.080 [1] > Before Task.Run
20:25:50.101 [1] > After Task.Run
20:25:50.102 [4] > --Starting work
20:25:51.101 [1] > Before Task.WhenAll
20:25:52.101 [1] > Before await whenAllTask
20:25:53.103 [4] > --Finishing work
20:25:53.103 [4] > After await whenAllTask
The work is started 1 millisecond after creating the task
, although The Task.WhenAll
is invoked one whole second later.
The documentation of the Task.Run
method says:
Queues the specified work to run on the thread pool and returns a proxy for the task returned by
function
.
Under normal conditions the ThreadPool
is very responsive. It takes practically no time at all to execute the queued action.
CodePudding user response:
My issues is, I'd like to pass in functions that contain arguments that I can use for handling very easily without having to declare accessible objects in the current class I'm in.
Tasks only represent the return value of a method, they have no knowledge of the functions inputs.
If you're goal is to compile a generic list of Functions without knowledge of their arguments until later, then you're issue is you don't want a List<Task>
you want a List<Func<...>>
Even then for this to work well without abusing things like reflection, all your Funcs need to have the same signature.
So lets say you have:
async Task FunctionOne(string myString, int myInt) { ... }
async Task FunctionTwo(string myString, int myInt) { ... }
But you don't have the string
and int
args read just yet to pass in, then you can store them as:
var myDelegates = new List<Func<string, int, Task>> { FunctionOne, FunctionTwo };
And then when you actually have all your args ready you could use Linq to fire off the tasks.
List<Task> = myTasks = myDelegates.Select(d => d(myStringArg, myIntArg)).ToList();
await Task.WhenAll(MyTasks);
Keep in mind Task.WhenAll(...)
does not fire off the Tasks. The moment you have a Task
object in memory it has already started.
It just happens to be in your code that Task.WhenAll
is the next line of code, but the moment you do:
var myTask = SomeAsyncFunc(..args...)
The task has started.
So up above, the moment we call specifically .ToList()
(which hydrates the IEnumerable
into a List
and invokes it greedily) the tasks all start up.
All await Task.WhenAll
does is say "Block execution until all these tasks are complete"
The tasks may even already be complete before you have even invoked Task.WhenAll
if they are fast ones!