I'm trying to implement self-hosted web service using asp.net core 2.1 and got stuck with the problem of implementing background long-time execution tasks.
Due to the high CPU load and time consumption of each ProcessSingle method (in the code snippet below), I would like to limit the number of executing simultaneous tasks. But as I can see all tasks in Parallel.ForEachstart almost immediately, despite the fact that I set MaxDegreeOfParallelism = 3
My code is (it's a simplified version):
public static async Task<int> Work()
{
var id = await CreateIdInDB() // async create record in DB
// run background task, don't wait when it finishes
Task.Factory.StartNew(async () => {
Parallel.ForEach(
listOfData,
new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = 3 },
async x => await ProcessSingle(x));
});
// return created id immediately
return id;
}
public static async Task ProcessSingle(MyInputData inputData)
{
var dbData = await GetDataFromDb(); // get data from DB async using Dapper
// some lasting processing (sync)
await SaveDataToDb(); // async save processed data to DB using Dapper
}
If I understand correctly, the problem is in async x => await ProcessSingle(x) inside Parallel.ForEach, isn't it?
Could someone describe please, how it should be implemented in the right way?
Update
Due to some kind of ambiguity in my question, it's necessary to focus on the main aspects:
There are three parts in
ProcessSinglemethod:getting data from DB async
make long-time high CPU-loaded math calculations
save results to DB async
The problem consists of two separate:
How to decrease CPU usage (by running not more than three math simultaneous calculations for example)?
How to keep the structure of the
ProcessSinglemethod - keep them async because of async DB calls.
Hope it would be more clear now.
P.S. The suitable answer has been already given, it works (especially thanks to @MatrixTai). This update has been written for general clarification.
Parallel.ForEachwithasync/await"The whole idea behind Parallel.ForEach() is that you have a set of threads and each thread processes part of the collection. As you noticed, this doesn't work with async-await, where you want to release the thread for the duration of the async call"ProcessSingle. So the only way is to stop usingParallel.ForEachand start using something likeTask.WhenAll(/* call all ProcessSingle */)? But in that case, there is no built-in way to limit concurrent threads (as I remember, I've seen some custom implementations)?async/awaitbut you can also throttle. ;)Parallel.Foreahwith async/await. It's meant for data parallelism - it's job is to partition the incoming data and assign each partition to a single task. What you ask though is already built into theActionBlock<T>class.