Run multiple instances of same method asynchronously?

11,126

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 awaityou'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.

Share:
11,126
Zameer Ansari
Author by

Zameer Ansari

Updated on June 04, 2022

Comments

  • Zameer Ansari
    Zameer Ansari almost 2 years

    My requirement is quite weird.

    I have SomeMethod() which calls GetDataFor().

    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 for GetDataFor(i) to complete before calling GetDataFor(i+1).

    In other words I need to:

    • call GetDataFor() for each i+1 immediately after successfully calling i (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 for awaiting? It is getting over-written.
    • Is it blatantly wrong way to do this? So, how do do it right?
  • poke
    poke about 8 years
    And your answer to the question (“how to run this in parallel”) is…?
  • AIDA
    AIDA about 8 years
    @student, does the GetDataFor return data itself?
  • Zameer Ansari
    Zameer Ansari about 8 years
    Yes, it returns data
  • AIDA
    AIDA about 8 years
    This is no different than using the await keyword.
  • AIDA
    AIDA about 8 years
    Good call on the use of parallelism, the OP did mention at the same time.
  • sr28
    sr28 about 8 years
    This would only run things in parallel based on the number of cores you have. If you have 1 core then nothing would improve.
  • AIDA
    AIDA about 8 years
    I 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
    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
    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
    Zameer Ansari about 8 years
    @sr28 Can you please explain why will it not execute in parallel?
  • sr28
    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
    AIDA about 8 years
    I missed the tasks.Add!
  • Zameer Ansari
    Zameer Ansari about 8 years
    I need to wait until all the GetDataFor() is completed running.
  • Zameer Ansari
    Zameer Ansari about 8 years
    Also, does it make things heavy? My visual studio is unable to handle F5 and I have to kill it.
  • Zameer Ansari
    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
    YK1 about 8 years
    @student: if GetDataFor(i) and GetDataFor(i+1) are totally independent and don't modify any shared data, there is no thread safety concern.
  • Arturo Menchaca
    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 add Parallel.For behavior based on the number of cores.