提问人:RobC 提问时间:10/9/2023 最后编辑:TylerHRobC 更新时间:10/10/2023 访问量:62
从 Parallel.ForEachAsync() 线程中调用接口方法是否安全?
Is calling interface method from within Parallel.ForEachAsync() thread safe?
问:
这里有一些代码,希望能清楚地说明我所说的情况:
public class Processor
{
private readonly IRepository _repo;
private readonly IApiSrevice _apiService
private readonly _mapper;
public Processor(IRepository repo, IApiSrevice apiService, IMapper mapper)
{
_repo = repo;
_apiService = apiService
_mapper = mapper;
}
public async Task<IEnumerable<Thing>> ProcessStuff(IEnumerable<MyDto> dtos)
{
var people = await _apiService.GetPeople();
ConcurrentBag<Location> things = new();
var options = new ParallelOptions { MaxDegreeOfParallelism = 3 };
await Parallel.ForEachAsync(people, options, async(person, token ) =>
{
var locations = await _apiService.GetLocations(person.Id);
IEnumerable<Thing> newThings = _mapper.Map(locations);
// maybe there's a repo call in here somewhere
// _repo.AddThings(newThings);
foreach(var thing in newThings)
{
things.Add(thing)
}
});
return things;
}
}
我认为仅仅因为接口(隐藏实现)的性质,从并行循环中调用任何方法都是一个坏主意:实现可能具有非线程安全的方法。
如果是这样,如何调用接口上的方法?我已经做了相当多的测试,包括标准的foreach循环和标准foreach循环,我得到了相同的结果,但我不确定这是我可以指望的。不过,使用 Parallel 循环和 6 度并行度运行所需的时间要少得多。Parallel.ForEachAsync()
答:
接口只是抽象合约的一种方式,因为任何抽象都可能变得漏洞百出,因此您可能需要更深入地研究实现。
在这种特殊情况下,它们与任何功能封装都没有太大区别——无论你是调用在接口上定义的方法,还是在某个类中定义的方法,你仍然需要了解它的作用和工作原理,或者至少,如果你想在潜在的多线程上下文中使用它,它提供了什么并发保证(即 在这种情况下)。此外(假设您希望获得一些性能提升),您肯定需要知道实际实现是如何工作的,以了解从并行化中可以获得多少收益。Parallel.ForEachAsync
一个不太关心接口实现内部工作的选项是为每个处理程序创建一个 DI 范围(假设您正在使用它)——例如,通过注入 IServiceScopeFactory
并用于创建范围和解析依赖项(也可以封装到一些“迭代处理程序”中),尽管通常仍然建议了解实现的作用。
附言
ConcurrentBag
可能不是在这里使用的最佳选择。-
我已经做了相当多的测试,包括 Parallel.ForEachAsync() 和标准的 foreach 循环,并得到了相同的结果......
TBH:我预计设置为 3 的性能增益“均匀”,但如果没有看到实际实现,就很难说。
MaxDegreeOfParallelism
评论
SELECT * FROM Person where UnindexedID=@id
SELECT ... WHERE unindexedid in (@id1, @id2,...,@id6)
ID
IN