0

I've never attempted to use WaitAll() or WhenAll() when running async functionality. After looking at many documentations, SO posts, and tutorials, I haven't found enough information for this, so here I am.

I'm trying to figure out the best/proper way(s) to do the following:

Using EF6, get data as List<Entity>. Iterate through each Entity and call an external API to perform some action. External API returns data per Entity which I need to store on the same Entity.

Currently I have built (not tested) the following (without the error handling code):

public IEnumerable<Entity> Process() {
    bool hasChanged = false;
    var data = _db.Entity.Where(x => !x.IsRegistered);
    
    foreach (var entity in data) {
        var result = await CallExternalApi(entity.Id, entity.Name);

        entity.RegistrationId = result.RegistrationId;
        entity.IsRegistered = true;
        _db.Entry(entity).State = EntityState.Modified;
        hasChanges = true;
    }

    if (hasChanges) {
        uow.Commit();
    }

    return data;
}

I feel like I may be able to take advantage of some other functionality/feature in async, but if I can I'm not sure how to implement it here.

Any guidance is really appreciated.

Update

The API I'm calling is the Zoom Api to add Registrants. While they do have an route to batch add Registrants, it does not return the RegistrantId and the Join Url I need.

RoLYroLLs
  • 3,113
  • 4
  • 38
  • 57
  • Do you want to call the `CallExternalApi` method for all `Entity` objects concurrently? Are you sure that the external API does not have any limitations regarding concurrent usage? – Theodor Zoulias Nov 10 '21 at 00:12
  • Good question, while the API does have a batch update (I updated the question to include which API it is), it does not return the same data on batch than it does per entity. As for concurrent calls, I don't think I have the need, but wanted to start investigating how, if it all, this can work in my scenario. I don't expect to have to do this for more than 10 or 20 entities at a time (at this moment). – RoLYroLLs Nov 10 '21 at 15:28
  • RoLYroLLs if you are fine with executing the external API calls *serially*, then your current code is OK. Neither the `Task.WhenAll` nor the `Task.WaitAll` are useful to you in this case. These APIs are facilitating *concurrent* asynchronous operations. – Theodor Zoulias Nov 10 '21 at 16:32
  • @TheodorZoulias thank you! – RoLYroLLs Nov 10 '21 at 18:34

1 Answers1

1

First, figure out if your external API might have a way to get all the items you want in a batch. If it does, use that instead of sending a whole bunch of requests.

If you need to send a separate request for each item, but want to do it concurrently, you could do this:

public async Task<IReadOnlyCollection<Entity>> Process() {
    var data = _db.Entity.Where(x => !x.IsRegistered).ToList();

    if(!data.Any()) { return data; }

    var entityResultTasks = data
        .Select(async entity => new { entity, result = await CallExternalApi(entity.Id, entity.Name) })
        .ToList();
    var entityResults = await Task.WhenAll(entityResultTasks);
    foreach (var entityResult in entityResults) {
        var entity = entityResult.entity;
        var result = entityResult.result;

        entity.RegistrationId = result.RegistrationId;
        entity.IsRegistered = true;
        _db.Entry(entity).State = EntityState.Modified;
    }
    uow.Commit();
    return data;
}

You will want to watch out for possible concurrency limits on the target source. Consider using Chunk to break your work into batches of acceptable sizes, or leveraging a semaphore or something to throttle the number of calls you're making.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Thank you. I'll look into this. I did add an update to the question concerning performing batch operations, but the resulting data is limited. – RoLYroLLs Nov 10 '21 at 15:33
  • re: `Chunk`. This app is unfortunately still running on .Net 4.7.2, and planed on being upgraded in the next year. Looking at the documentation it doesn't seem to support this. Are there alternatives? I'll continue looking as well. – RoLYroLLs Nov 10 '21 at 15:36
  • @RoLYroLLs the microsoft documentation defaults to the latest version. You should have access to `.Chunk` [Link to 4.7.2](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.chunk?view=net-6.0&viewFallbackFrom=netframework-4.7.2) – Mark C. Nov 10 '21 at 15:38
  • @MarkC. ah. I guess I misunderstood the message saying `The requested page is not available for .NET Framework 4.7.2. You have been redirected to the newest product version this page is available for.`. Thank you. – RoLYroLLs Nov 10 '21 at 15:43
  • 1
    @RoLYroLLs the `Chunk` is not available on .NET Framework. You can look [here](https://stackoverflow.com/questions/13731796/create-batches-in-linq) for alternatives to `Chunk`. You can also look [here](https://stackoverflow.com/questions/10806951/how-to-limit-the-amount-of-concurrent-async-i-o-operations) for better throttling mechanisms than chunking. – Theodor Zoulias Nov 10 '21 at 16:24
  • @TheodorZoulias that's what I thought. Thank you. – RoLYroLLs Nov 10 '21 at 18:33