这里有一些代码只是为了希望能够弄清楚我正在谈论的情况:
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;
}
}
我认为仅仅因为接口(隐藏实现)的性质,从并行循环中调用其中的任何方法都是一个坏主意:实现可能具有非线程安全的方法。
如果是这样,我如何调用接口上的方法?我已经使用
Parallel.ForEachAsync()
和标准 foreach 循环进行了大量测试,并且得到了相同的结果,但我不确定这是否是我可以信赖的。不过,使用并行循环和 6 度并行度运行所需的时间要少得多。
接口只是抽象契约的一种方式,因为任何抽象都可能变得泄漏,因此您可能需要更深入地研究实现。
在这种特殊情况下,它们与任何功能封装都没有什么不同 - 如果您调用在接口或某个类中定义的方法并不重要,您仍然需要了解它的作用和工作原理,或者至少了解并发性保证它提供了您是否想在潜在的多线程上下文中使用它(即本例中的
Parallel.ForEachAsync
)。另外(假设您希望获得一些性能提升)您肯定需要知道实际实现是如何工作的,以了解并行化可以带来多少好处。
不太关心接口实现的内部工作的一个选择是为每个处理程序创建一个 DI 范围(假设您正在使用它) - 例如通过注入
IServiceScopeFactory
并使用来创建范围和解析依赖项(也可以被封装到一些“迭代处理程序”中),尽管一般来说仍然建议了解实现的作用。
附注
ConcurrentBag
可能不是在这里使用的最佳选择。
我已经使用 Parallel.ForEachAsync() 和标准 foreach 循环进行了大量测试,并得到了相同的结果...
说实话,我预计
MaxDegreeOfParallelism
设置为 3 时性能会“均匀”增长,但在没有看到实际实施的情况下很难说。