使用循环中的任务调用 rest API 并获得良好的性能

问题描述 投票:0回答:1

我有一个包含大约 8000 条员工记录的文件,我需要通过为每条记录调用一个 rest API 来处理它。顺序 API 调用需要很多时间,所以我想在任务中异步调用它们并等待所有任务完成。我计划同时运行三个任务。

我写了下面的代码,但我担心竞争条件或多线程问题,因为我正在更新任务中的员工实体。我的理解是我可以更新实体但不能调用 dbcontext 方法。我知道 DBContext 不是线程安全的,所以我在任务循环之外调用

SaveChanges
。任何人都可以查看我的代码并让我知道我是否做对了?这是我的伪代码:

private async TempMethod()
{
    var dbcontext = new DBContext();
    var employees = dbcontext.Employees.ToList();

    var allTasks = new List<Task<APIResult>();

    var throttler = new SemaphoreSlim(initialCount: 3);

    foreach (var employee in employees)
    {
        await throttler.WaitAsync();
        allTasks.Add(
                       Task.Run(async () =>
                       {
                           try
                           {
                               var apiResult = await apiClient.Update(employee);
                               if (apiResult == "Success")
                               {
                                   employee.lastupdatedby = "Importer";
                               }

                               apiResult.recordNumber = employee.recordNumber;
                               return apiResult;
                           }
                           finally
                           {
                                   throttler.Release();
                           }
                       }
                       );
    }

    var results = await Task.WhenAll(allTasks);

    foreach (var result in results)
    {
        dbcontext.APIResults.Add(result);
    }

    //Save both Updated Employee and the Result entitities.
    dbcontext.SaveChangesAsync();
}
c# performance asynchronous concurrency .net-4.6.1
1个回答
0
投票

在这些条件下,您的代码对我来说似乎是正确的:

  1. employee.lastupdatedby
    apiResult.recordNumber
    要么是公共领域,要么是私有领域支持的琐碎属性(无副作用)。
  2. apiClient
    是其实例的类是线程安全的。
  3. 无论如何,你都想处理所有的
    employees
    ,即使是在早期异常的情况下。换句话说,如果出现错误,您不想尽快完成。
  4. 如果出现异常,您可以只更新一部分
    employees
    lastupdatedby
  5. 如果有多个异常,您可以只传播其中一个异常(
    employee
    列表中第一个
    employees
    的异常失败)。

作为旁注,我个人更愿意在单独的辅助方法中抽象并行化/节流功能,而不是将线程和 TPL 机制与我的应用程序代码混合。我想链接到

ForEachAsync
的高质量实现,该实现返回结果并与 .NET 4.6.1 兼容,但我找不到任何链接。 Jon Skeet 的实现 here 是不错的,但在异常情况下没有理想的行为,并且它不保留结果的顺序。

© www.soinside.com 2019 - 2024. All rights reserved.