Here's some code just to hopefully make clear the situation I'm talking about:
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;
}
}
I think that just because of the nature of interfaces (hidden implementations) calling any method on one from within a Parallel loop is a bad idea: implementations might have methods that aren't thread-safe.
If so, how can I call out to methods on interfaces? I've done quite a bit of testing, both with Parallel.ForEachAsync() and a standard foreach loop, and I get identical results, but I'm not sure this is something I can count on. Running with the Parallel loop and 6 degrees of parallelism takes significantly less time, though.
Interfaces are just a way to abstract a contract, as any abstraction it can become leaky so you might need to dig deeper into the implementation.
In this particular case they are not that different from any functionality encapsulation - it does not matter if you are calling a method defined on interface or in some class you still need to understand what it does and how it works or at least what concurrency guarantees it provides if you want to use it in potentially multithreaded context (i.e.
Parallel.ForEachAsyncin this case). Also (assuming you want to have some perfromance gains) you definitely will need to know how the actual implementation works to understand how much can be gained from parallelization.One option to care a bit less about internal workings of interface implementation is to create a DI scope (assuming you are using that) per handler - for example by injecting
IServiceScopeFactoryand using to create scope and resolve dependencies (also can be encapsulated into some "iteration handler"), though in general it is still recommended to understand what the implementation does.P.S.
ConcurrentBagmight not be the best option to use here.TBH I would expect perfromance gains "even" for
MaxDegreeOfParallelismset to 3 but without seeing actual implementation it is hard to tell.