Run multiple instances of same method asynchronously?
Solution 1
You can use Parallel.For
:
public void SomeMethod()
{
Parallel.For(0, 100, i =>
{
var data = GetDataFor(i);
//Do something
});
}
public data GetDataFor(int i)
{
//generate data for i
return data;
}
EDIT:
The syntax of a parallel loop is very similar to the for
and foreach
loops you already know, but the parallel loop runs faster on a computer that has available cores. Another difference is that, unlike a sequential loop, the order of execution isn't defined for a parallel loop. Steps often take place at the same time, in parallel. Sometimes, two steps take place in the opposite order than they would if the loop were sequential. The only guarantee is that all of the loop's iterations will have run by the time the loop finishes.
For parallel loops, the degree of parallelism doesn't need to be specified by your code. Instead, the run-time environment executes the steps of the loop at the same time on as many cores as it can. The loop works correctly no matter how many cores are available. If there is only one core, the performance is close to (perhaps within a few percentage points of) the sequential equivalent. If there are multiple cores, performance improves; in many cases, performance improves proportionately with the number of cores.
You can see a more detailed explanation here.
Solution 2
When using async
await
you're essentially saying "whilst waiting for this task to finish please go off and do some independent work that doesn't rely on this task". As you don't care about waiting for GetDataFor to finish you don't really want to use async
await
.
This previous question seems to have a very similar request as yours. With that in mind I think you should be able to do something like this:
public void SomeMethod()
{
Task.Run(() => GetDataFor(i));
}
Basically, this assumes you don't need to wait for the GetDataFor to finish before doing anything else, it's literally 'fire and forget'.
With regards to Parallel.For, you are likely to see some improvement in performance so long as you have more than 1 core. If not, you will probably see an ever so slight decrease in performance (more overhead). Here's an article that helps explain how it works.
UPDATE
Following your comment then I would suggest something like:
var TList = new List<Task>();
for (var i = 0; i < 100; i++)
{
TList.Add(Task.Run(() => GetDataFor(i)));
}
await Task.WhenAll(TList);
Here's a useful question that highlights why you might want to use WhenAll instead of WaitAll.
You might want to include some checking around the status of completion of the tasks to see which failed (if any). See here for an example.
Solution 3
There's a couple of different approaches.
First, you could keep it synchronous and just execute them in parallel (on different threads). Parallel LINQ is better than Parallel
if you want to collect all the results in the calling method before continuing:
public data[] SomeMethod()
{
return Enumerable.Range(0, 100)
.AsParallel().AsOrdered()
.Select(GetDataFor).ToArray();
}
Second, you could make it asynchronous. To make something truly asynchronous, you need to start at the lowest level (in this case, "call a remote API" and "store to database") and make that asynchronous first. Then you can make GetDataFor
asynchronous:
public async Task<data> GetDataForAsync(int i)
{
await .. //call a remote API asynchronously
await .. //store to database asynchronously
return data;
}
Then you can make SomeMethod
asynchronous as well:
public Task<data[]> SomeMethodAsync()
{
return Task.WhenAll(
Enumerable.Range(0, 100).Select(GetDataForAsync)
);
}
Making the code asynchronous is more work - more of the code has to change - but it's better in terms of scalability and resource use.
Solution 4
I would instead add each of the tasks to a collection and then await on the entire collection AFTER the loop.
Awaiting inside of a loop like that will create lots of continuations and more overhead than desirable including waiting for each call to finish before continuing the loop I believe.
Take a look at awaiting Task.WaitAll instead.
If instead the value of each task is important to process then look at awaiting Task.WhenAll and then read the results of each Task into your return collection.
Solution 5
The code actually makes no sense.
How task will distinguish between different calls for awaiting? It is getting over-written.
It does not get overwritten. Because...
for(int i = 0; i < 100; i++) {
var task = Task.Run(() => GetDataFor(i));
var data = await task;
}
This is WAITING for every request to finish before continuing the loop. Await waits for the end.
Which means the whole task thing is irrelevant - nothing happens in parallel here. You can cut some minor overhead by doing it without a task.
I suspect the OP wanted to achieve something that he simply did not and he was not spending enough time debugging to realize he has single threaded the whole loop again.
Zameer Ansari
Updated on June 04, 2022Comments
-
Zameer Ansari almost 2 years
My requirement is quite weird.
I have
SomeMethod()
which callsGetDataFor()
.public void SomeMethod() { for(int i = 0; i<100; i++) { var data = GetDataFor(i); } } public data GetDataFor(int i) { //call a remote API //to generate data for i //store to database return data; }
For each
i
, the end result will always be different. There is no need to wait forGetDataFor(i)
to complete before callingGetDataFor(i+1)
.In other words I need to:
- call
GetDataFor()
for eachi+1
immediately after successfully callingi
(Calling them in parallel looks impossible) - wait until all the 100 instances of
GetDataFor()
are completed running - leave the scope of
SomeMethod()
Following YK1's answer, I have tried to modify it like this:
public async Task<void> SomeMethod() { for(int i = 0; i < 100; i++) { var task = Task.Run(() => GetDataFor(i)); var data = await task; } }
It didn't thrown any errors but I need to understand the concept behind this:
- How
task
will distinguish between different calls forawait
ing? It is getting over-written. - Is it blatantly wrong way to do this? So, how do do it right?
- call
-
poke about 8 yearsAnd your answer to the question (“how to run this in parallel”) is…?
-
AIDA about 8 years@student, does the GetDataFor return data itself?
-
Zameer Ansari about 8 yearsYes, it returns
data
-
AIDA about 8 yearsThis is no different than using the await keyword.
-
AIDA about 8 yearsGood call on the use of parallelism, the OP did mention at the same time.
-
sr28 about 8 yearsThis would only run things in parallel based on the number of cores you have. If you have 1 core then nothing would improve.
-
AIDA about 8 yearsI added the WhenAll use case that would handle that scenario, but @Arturo-Menchaca may have the better case for you depending on if you want the code to TRULY run side by side or simply non-blocking asynchronous.
-
T. Yates about 8 years@OvanCrone That is not true. All of the tasks will run asynchronously. The OP said GetDataFor was allowed to be async. The Task.WaitAll prevents SomeMethod from returning until all are complete, if that's what you mean, but the OP did not say the containing method needed to be async.
-
T. Yates about 8 years@OvanCrone If you have a minute to expand on your comment, I would appreciate it, especially after the downvote :-) I have run my code in a simple app to verify that all GetDataFor tasks and all the ContinueWith tasks run asynchronously and in parallel; there is no waiting for one task to finish before spawning another. If I am missing something, I am open to constructive criticism!
-
Zameer Ansari about 8 years@sr28 Can you please explain why will it not execute in parallel?
-
sr28 about 8 years@student - no, it just wouldn't run in parallel. It would run like a normal for loop, so no advantage. It basically depends on the number of cores. So if you have 2 cores, it would run one call to GetDataFor on 1 and the next call on the other (running in parallel). It would then need to wait until 1 of those calls was done before starting another. If GetDataFor is a really long running process and you only have 2 cores then you will probably only see close to 50% improvement.
-
AIDA about 8 yearsI missed the tasks.Add!
-
Zameer Ansari about 8 yearsI need to wait until all the
GetDataFor()
is completed running. -
Zameer Ansari about 8 yearsAlso, does it make things heavy? My visual studio is unable to handle F5 and I have to kill it.
-
Zameer Ansari about 8 years@ArturoMenchaca This answered my weird concern, Can you please add sr28's point? My only concern is it's thread safety. Will it overwrite things under heavy load?
-
YK1 about 8 years@student: if
GetDataFor(i)
andGetDataFor(i+1)
are totally independent and don't modify any shared data, there is no thread safety concern. -
Arturo Menchaca about 8 years@student: as @YK1 says, thread safety is completely dependent on the implementation of
GetDataFor(i)
.Parallel.For
just executes a for loop in which iterations may run in parallel. I edited my answer to addParallel.For
behavior based on the number of cores.