我有一个包含大约 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();
}
在这些条件下,您的代码对我来说似乎是正确的:
employee.lastupdatedby
和 apiResult.recordNumber
要么是公共领域,要么是私有领域支持的琐碎属性(无副作用)。apiClient
是其实例的类是线程安全的。employees
,即使是在早期异常的情况下。换句话说,如果出现错误,您不想尽快完成。employees
的lastupdatedby
。employee
列表中第一个 employees
的异常失败)。作为旁注,我个人更愿意在单独的辅助方法中抽象并行化/节流功能,而不是将线程和 TPL 机制与我的应用程序代码混合。我想链接到
ForEachAsync
的高质量实现,该实现返回结果并与 .NET 4.6.1 兼容,但我找不到任何链接。 Jon Skeet 的实现 here 是不错的,但在异常情况下没有理想的行为,并且它不保留结果的顺序。