I'm using retrieving message from Azure Service Bus Topic currently and running into a performance issue. I'm requesting messages in batches of 1000 from ASB. The code fetches anywhere between 200-300 messages in first few iterations and then the number of messages retrieved comes down to 1 per iteration. I pull approximately 50K messages which the application is able to retrieve about 2 hours. Every time I stop the application and re-run it, it behaves the same. First 4-5K records get processed in 1-2 mins, but the rest take lot longer. How can I make my code to retrieve more messages in every batch?
Sample Code with batch size set to 50
using Microsoft.ServiceBus.Messaging;
public static Microsoft.ServiceBus.Messaging.SubscriptionClient subscriptionClient;
public async Task startProcess()
{
await Run();
}
public async Task Run()
{
subscriptionClient = Microsoft.ServiceBus.Messaging.SubscriptionClient.CreateFromConnectionString(ServiceBusConnectionString, TopicName, subscriptionname);
for(int counter=1;counter<= 5;)
{
tasks.Add(Task.Factory.StartNew(() => PullMessages(counter))) ;
taskCounter++;
}
var arr = tasks.ToArray();
Task.WaitAll(arr);
}
public async Task PullMessages(object ThreadNumber)
{
bool retry = true;
do
{
subscriptionClient.PrefetchCount = 1000;
iterationCount++;
var messages = await subscriptionClient.ReceiveBatchAsync(50);
logger.Info("{0} messages retrieved for iteration {1} for thread {2}", messages.Count().ToString(), currentiteration.ToString(),ThreadNumber);
await subscriptionClient.CompleteBatchAsync(messages.Select(m => m.LockToken));
logger.Info("Completed iteration {0} for thread {1}", currentiteration.ToString(), ThreadNumber);
if (messages != null && messages.Count() > 0)
{
retry = true;
}
else
{
retry = false;
}
} while (retry);
}
Log:
2020-07-21 19:06:48.3177 Info 50 messages retrieved for iteration 2 for thread 1
2020-07-21 19:06:48.3177 Info 50 messages retrieved for iteration 3 for thread 3
2020-07-21 19:06:48.3177 Info Completed iteration 4 for thread 5
2020-07-21 19:06:48.3177 Info 1 messages retrieved for iteration 6 for thread 5
2020-07-21 19:06:48.3177 Info 50 messages retrieved for iteration 5 for thread 4
2020-07-21 19:06:48.3177 Info 50 messages retrieved for iteration 1 for thread 2
2020-07-21 19:06:48.6010 Info Completed iteration 3 for thread 3
2020-07-21 19:06:48.6010 Info Completed iteration 5 for thread 4
2020-07-21 19:06:48.6010 Info Completed iteration 2 for thread 1
2020-07-21 19:06:48.6010 Info Completed iteration 6 for thread 5
2020-07-21 19:06:48.6225 Info Completed iteration 1 for thread 2
2020-07-21 19:06:48.8367 Info 1 messages retrieved for iteration 7 for thread 3
2020-07-21 19:06:49.0683 Info 10 messages retrieved for iteration 8 for thread 4
2020-07-21 19:06:49.0934 Info Completed iteration 7 for thread 3
2020-07-21 19:06:49.3005 Info 1 messages retrieved for iteration 9 for thread 1
2020-07-21 19:06:49.3491 Info Completed iteration 8 for thread 4
2020-07-21 19:06:49.5301 Info 1 messages retrieved for iteration 10 for thread 5
2020-07-21 19:06:49.5612 Info Completed iteration 9 for thread 1
2020-07-21 19:06:49.7868 Info Completed iteration 10 for thread 5
2020-07-21 19:06:49.7868 Info 1 messages retrieved for iteration 11 for thread 2
2020-07-21 19:06:50.0586 Info Completed iteration 11 for thread 2
2020-07-21 19:06:50.2511 Info 1 messages retrieved for iteration 12 for thread 3
2020-07-21 19:06:50.5141 Info Completed iteration 12 for thread 3
2020-07-21 19:06:50.5838 Info 1 messages retrieved for iteration 13 for thread 4
2020-07-21 19:06:50.8426 Info Completed iteration 13 for thread 4
2020-07-21 19:06:50.9471 Info 1 messages retrieved for iteration 14 for thread 1
2020-07-21 19:06:51.2014 Info Completed iteration 14 for thread 1
2020-07-21 19:06:51.2475 Info 1 messages retrieved for iteration 15 for thread 5
2020-07-21 19:06:51.5003 Info Completed iteration 15 for thread 5
2020-07-21 19:06:51.6126 Info 1 messages retrieved for iteration 16 for thread 2
2020-07-21 19:06:51.8709 Info Completed iteration 16 for thread 2
2020-07-21 19:06:51.9732 Info 1 messages retrieved for iteration 17 for thread 3
2020-07-21 19:06:52.2269 Info Completed iteration 17 for thread 3
2020-07-21 19:06:52.3523 Info 1 messages retrieved for iteration 18 for thread 4
2020-07-21 19:06:52.6095 Info Completed iteration 18 for thread 4
2020-07-21 19:06:52.7559 Info 1 messages retrieved for iteration 19 for thread 1
2020-07-21 19:06:53.0392 Info Completed iteration 19 for thread 1
2020-07-21 19:06:53.1315 Info 1 messages retrieved for iteration 20 for thread 5
2020-07-21 19:06:53.3902 Info Completed iteration 20 for thread 5
2020-07-21 19:06:53.4929 Info 1 messages retrieved for iteration 21 for thread 2
2020-07-21 19:06:53.9374 Info Completed iteration 21 for thread 2
2020-07-21 19:06:53.9374 Info 1 messages retrieved for iteration 22 for thread 3
2020-07-21 19:06:54.1891 Info 1 messages retrieved for iteration 23 for thread 4
2020-07-21 19:06:54.5142 Info Completed iteration 22 for thread 3
2020-07-21 19:06:54.5142 Info Completed iteration 23 for thread 4
2020-07-21 19:06:54.5735 Info 1 messages retrieved for iteration 24 for thread 1
2020-07-21 19:06:54.8614 Info Completed iteration 24 for thread 1
2020-07-21 19:06:55.0985 Info 1 messages retrieved for iteration 25 for thread 5
2020-07-21 19:06:55.3322 Info 1 messages retrieved for iteration 26 for thread 2
2020-07-21 19:06:55.3994 Info Completed iteration 25 for thread 5
2020-07-21 19:06:55.7143 Info Completed iteration 26 for thread 2
2020-07-21 19:06:55.7143 Info 1 messages retrieved for iteration 27 for thread 3
2020-07-21 19:06:56.0099 Info Completed iteration 27 for thread 3
2020-07-21 19:06:56.3277 Info 1 messages retrieved for iteration 28 for thread 4
2020-07-21 19:06:56.5589 Info 1 messages retrieved for iteration 29 for thread 1
2020-07-21 19:06:56.6247 Info Completed iteration 28 for thread 4
2020-07-21 19:06:56.9426 Info Completed iteration 29 for thread 1
2020-07-21 19:06:56.9426 Info 1 messages retrieved for iteration 30 for thread 5
2020-07-21 19:06:57.2370 Info Completed iteration 30 for thread 5
2020-07-21 19:06:57.2957 Info 1 messages retrieved for iteration 31 for thread 2
2020-07-21 19:06:57.5569 Info Completed iteration 31 for thread 2
2020-07-21 19:06:57.6317 Info 1 messages retrieved for iteration 32 for thread 3
2020-07-21 19:06:58.1697 Info Completed iteration 32 for thread 3
2020-07-21 19:06:58.1697 Info 1 messages retrieved for iteration 33 for thread 4
2020-07-21 19:06:58.3963 Info 1 messages retrieved for iteration 34 for thread 1
2020-07-21 19:06:58.4675 Info Completed iteration 33 for thread 4
2020-07-21 19:06:58.7874 Info Completed iteration 34 for thread 1
2020-07-21 19:06:58.7874 Info 1 messages retrieved for iteration 35 for thread 5
2020-07-21 19:06:59.0824 Info Completed iteration 35 for thread 5
2020-07-21 19:06:59.1997 Info 1 messages retrieved for iteration 36 for thread 2
2020-07-21 19:06:59.4628 Info Completed iteration 36 for thread 2
2020-07-21 19:06:59.5767 Info 1 messages retrieved for iteration 37 for thread 3
2020-07-21 19:06:59.8384 Info Completed iteration 37 for thread 3
2020-07-21 19:06:59.9965 Info 1 messages retrieved for iteration 38 for thread 4
2020-07-21 19:07:00.2579 Info Completed iteration 38 for thread 4
2020-07-21 19:07:00.4314 Info 1 messages retrieved for iteration 39 for thread 1
2020-07-21 19:07:00.6886 Info Completed iteration 39 for thread 1
2020-07-21 19:07:00.7793 Info 1 messages retrieved for iteration 40 for thread 5
2020-07-21 19:07:01.0557 Info Completed iteration 40 for thread 5
2020-07-21 19:07:01.1719 Info 1 messages retrieved for iteration 41 for thread 2
2020-07-21 19:07:01.4312 Info Completed iteration 41 for thread 2
2020-07-21 19:07:01.5550 Info 1 messages retrieved for iteration 42 for thread 3
2020-07-21 19:07:01.8257 Info Completed iteration 42 for thread 3
2020-07-21 19:07:01.9706 Info 1 messages retrieved for iteration 43 for thread 4
2020-07-21 19:07:02.2463 Info Completed iteration 43 for thread 4
2020-07-21 19:07:02.3205 Info 1 messages retrieved for iteration 44 for thread 1
2020-07-21 19:07:02.5932 Info Completed iteration 44 for thread 1
2020-07-21 19:07:02.6965 Info 1 messages retrieved for iteration 45 for thread 5
2020-07-21 19:07:02.9570 Info Completed iteration 45 for thread 5
2020-07-21 19:07:03.0397 Info 1 messages retrieved for iteration 46 for thread 2
2020-07-21 19:07:03.2964 Info Completed iteration 46 for thread 2
2020-07-21 19:07:03.4604 Info 1 messages retrieved for iteration 47 for thread 3
2020-07-21 19:07:03.7161 Info Completed iteration 47 for thread 3
2020-07-21 19:07:03.8770 Info 1 messages retrieved for iteration 48 for thread 4
2020-07-21 19:07:04.1327 Info Completed iteration 48 for thread 4
2020-07-21 19:07:04.2976 Info 1 messages retrieved for iteration 49 for thread 1
2020-07-21 19:07:04.5538 Info Completed iteration 49 for thread 1
2020-07-21 19:07:04.6536 Info 1 messages retrieved for iteration 50 for thread 5
2020-07-21 19:07:04.9064 Info Completed iteration 50 for thread 5
2020-07-21 19:07:05.0138 Info 1 messages retrieved for iteration 51 for thread 2
2020-07-21 19:07:05.2715 Info Completed iteration 51 for thread 2
2020-07-21 19:07:05.3778 Info 1 messages retrieved for iteration 52 for thread 3
2020-07-21 19:07:05.6295 Info Completed iteration 52 for thread 3
2020-07-21 19:07:05.7348 Info 1 messages retrieved for iteration 53 for thread 4
2020-07-21 19:07:05.9970 Info Completed iteration 53 for thread 4
2020-07-21 19:07:06.1308 Info 1 messages retrieved for iteration 54 for thread 1
2020-07-21 19:07:06.3863 Info Completed iteration 54 for thread 1
2020-07-21 19:07:06.4485 Info 1 messages retrieved for iteration 55 for thread 5
2020-07-21 19:07:06.7028 Info Completed iteration 55 for thread 5
2020-07-21 19:07:06.8388 Info 1 messages retrieved for iteration 56 for thread 2
2020-07-21 19:07:07.1086 Info Completed iteration 56 for thread 2
2020-07-21 19:07:07.2214 Info 1 messages retrieved for iteration 57 for thread 3
2020-07-21 19:07:07.4797 Info Completed iteration 57 for thread 3
2020-07-21 19:07:07.6303 Info 1 messages retrieved for iteration 58 for thread 4
2020-07-21 19:07:07.8891 Info Completed iteration 58 for thread 4
2020-07-21 19:07:07.9782 Info 1 messages retrieved for iteration 59 for thread 1
2020-07-21 19:07:08.2421 Info Completed iteration 59 for thread 1
2020-07-21 19:07:08.3398 Info 1 messages retrieved for iteration 60 for thread 5
2020-07-21 19:07:08.5981 Info Completed iteration 60 for thread 5
2020-07-21 19:07:08.8066 Info 1 messages retrieved for iteration 61 for thread 2
2020-07-21 19:07:09.0616 Info Completed iteration 61 for thread 2
2020-07-21 19:07:09.2397 Info 1 messages retrieved for iteration 62 for thread 3
2020-07-21 19:07:09.4960 Info Completed iteration 62 for thread 3
2020-07-21 19:07:09.6018 Info 1 messages retrieved for iteration 63 for thread 4
2020-07-21 19:07:09.9001 Info Completed iteration 63 for thread 4
2020-07-21 19:07:10.0836 Info 1 messages retrieved for iteration 64 for thread 1
2020-07-21 19:07:10.3393 Info Completed iteration 64 for thread 1
2020-07-21 19:07:10.4576 Info 1 messages retrieved for iteration 65 for thread 5
2020-07-21 19:07:10.7138 Info Completed iteration 65 for thread 5
2020-07-21 19:07:10.8472 Info 1 messages retrieved for iteration 66 for thread 2
2020-07-21 19:07:11.1566 Info Completed iteration 66 for thread 2
2020-07-21 19:07:11.2157 Info 1 messages retrieved for iteration 67 for thread 3
2020-07-21 19:07:11.4675 Info Completed iteration 67 for thread 3
2020-07-21 19:07:11.5828 Info 1 messages retrieved for iteration 68 for thread 4
2020-07-21 19:07:11.8385 Info Completed iteration 68 for thread 4
2020-07-21 19:07:11.9688 Info 1 messages retrieved for iteration 69 for thread 1
2020-07-21 19:07:12.2347 Info Completed iteration 69 for thread 1
2020-07-21 19:07:12.3906 Info 1 messages retrieved for iteration 70 for thread 5
2020-07-21 19:07:12.6533 Info Completed iteration 70 for thread 5
2020-07-21 19:07:12.6990 Info 1 messages retrieved for iteration 71 for thread 2
2020-07-21 19:07:12.9680 Info Completed iteration 71 for thread 2
2020-07-21 19:07:13.1004 Info 1 messages retrieved for iteration 72 for thread 3
2020-07-21 19:07:13.3642 Info Completed iteration 72 for thread 3
2020-07-21 19:07:13.4772 Info 1 messages retrieved for iteration 73 for thread 4
2020-07-21 19:07:13.7345 Info Completed iteration 73 for thread 4
2020-07-21 19:07:13.8477 Info 1 messages retrieved for iteration 74 for thread 1
2020-07-21 19:07:14.1009 Info Completed iteration 74 for thread 1
2020-07-21 19:07:14.1812 Info 1 messages retrieved for iteration 75 for thread 5
2020-07-21 19:07:14.4479 Info Completed iteration 75 for thread 5
2020-07-21 19:07:14.5517 Info 1 messages retrieved for iteration 76 for thread 2
2020-07-21 19:07:14.8190 Info Completed iteration 76 for thread 2
2020-07-21 19:07:14.9361 Info 1 messages retrieved for iteration 77 for thread 3
2020-07-21 19:07:15.1989 Info Completed iteration 77 for thread 3
2020-07-21 19:07:15.3167 Info 1 messages retrieved for iteration 78 for thread 4
2020-07-21 19:07:15.5961 Info Completed iteration 78 for thread 4
2020-07-21 19:07:15.6242 Info 1 messages retrieved for iteration 79 for thread 1
Related
Can anyone explain the difference of using Task.Run with or without async and await like the code below, e.g. await Task.Run(async ()=>...)?
public class AsyncRun
{
public void Entry()
{
Test4().Wait();
}
private async Task Test4()
{
Console.WriteLine($"1 {DateTime.Now}");
await Task.Run(async () => await Get());
Console.WriteLine($"2 {DateTime.Now}");
Console.Read();
//1 23 / 05 / 2020 07:52:42
//Get 1 23 / 05 / 2020 07:52:42
//Get 2 23 / 05 / 2020 07:52:43
//2 23 / 05 / 2020 07:52:43
}
private Task Test3()
{
Console.WriteLine($"1 {DateTime.Now}");
Task.Run(async ()=> await Get());
Console.WriteLine($"2 {DateTime.Now}");
Console.Read();
return Task.CompletedTask;
//1 23 / 05 / 2020 07:47:24
//2 23 / 05 / 2020 07:47:24
//Get 1 23 / 05 / 2020 07:47:24
//Get 2 23 / 05 / 2020 07:47:25
}
private async Task Test2()
{
Console.WriteLine($"1 {DateTime.Now}");
await Task.Run(Get);
Console.WriteLine($"2 {DateTime.Now}");
Console.Read();
//1 23 / 05 / 2020 07:43:24
//Get 1 23 / 05 / 2020 07:43:24
//Get 2 23 / 05 / 2020 07:43:25
//2 23 / 05 / 2020 07:43:25
}
private void Test1()
{
Console.WriteLine($"1 {DateTime.Now}");
Task.Run(Get);
Console.WriteLine($"2 {DateTime.Now}");
Console.Read();
//1 23 / 05 / 2020 07:41:09
//2 23 / 05 / 2020 07:41:09
//Get 1 23 / 05 / 2020 07:41:09
//Get 2 23 / 05 / 2020 07:41:10
}
private Task Get()
{
Console.WriteLine($"Get 1 {DateTime.Now}");
Thread.Sleep(1000);
Console.WriteLine($"Get 2 {DateTime.Now}");
return Task.CompletedTask;
}
}
The difference is showing in your Console.WriteLines output.
When you use await Task.Run, you're waiting for the task you ran to finish, then continue the code execution, so you're getting the following logs:
// 1 23 / 05 / 2020 07:52:42
// Get 1 23 / 05 / 2020 07:52:42
// Get 2 23 / 05 / 2020 07:52:43
// 2 23 / 05 / 2020 07:52:43
When you don't wait the task you just ran, it's like you're running it and "forgetting" about it. This means that your code will continue to execute, and the task you ran will execute elsewhere, thus the logs are:
// 1 23 / 05 / 2020 07:47:24
// 2 23 / 05 / 2020 07:47:24
// ^ Your test code finished executing, without waiting for the Get task
// Get 1 23 / 05 / 2020 07:47:24
// Get 2 23 / 05 / 2020 07:47:25
Also, there is no difference between await Task.Run(async () => await Get())
and await Task.Run(Get) in terms of what actually happens. The only difference is that in the first one, you're creating another async lambda function to generate a Task while your Get method is already a Task, so you can use it directly.
General notes
The Console is not a good environment to learn ot test multitasking in any way, shape or form. One big issue is with MT keeping the application alive, without blocking continuation code. In consoles we have to do that manually. GUI's are the right environment, as they do it by accident. The EventQueue keeps the application alive, while still allowing I/O to happen.
Thread.Sleep() should not be used in Get. You should use the more agnostic Task.Delay(1000) instead. Using Thread classes here is going to cause unwanted side effects and even ruin the reason we have await in the first place.
Try it again in a GUI and without Sleep, as it will get you more meaningfull results.
What is Task
Task is just a construct to help with all forms of Multitasking. It is "agnostic" to how it is run. Get() can be executed asynchronously using ThreadPools. Asynchronously using async and await. Synchronously by just calling RunSynchronously(). Or asynchronously by calling RunSynchronously() from a Thread you manually started. And propably a few other ways, I can not remember yet.
Different kinds of Multitasking
Multithreading can be used to implement Multitasking. Technically we only need Multithreading with CPU bound work, but for the longest time it was the most expedient way to implement MT in general, so we tended to use it. A lot. Especially in cases where it was not nessesary.
Even fully undertanding why we did it, I think we overused to abused Thread based multitasking. And especially with GUIs, Multithreading causes some issues. Still, it was the easier way. So a lot of things, a lot of examples are still designed for Thread Based Multitasking.
Only recently, did we get async and await as alternative. async and await are a way of Multitasking without restorting to Mutlthreading. We always had and still have the option do the work of those two ourself. But that is quite code intensive and prone to error. Those two are easy and resolved reliably between the Compiler and the Runtime. So only now, do we start going back to forms of Threadless Multitasking.
Task.Run:
"Queues the specified work to run on the ThreadPool and returns a task or Task handle for that work." - https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run
So Run takes a task, and executes it via a ThreadPool - a form of Thread based mutltiasking.
Consider this code run on a CPU with 32 cores:
ParallelOptions po = new ParallelOptions();
po.MaxDegreeOfParallelism = 8;
Parallel.For(0, 4, po, (i) =>
{
Parallel.For(0, 4, po, (j) =>
{
WorkMethod(i, j); // assume a long-running method
});
}
);
My question is what is the actual maximum possibly concurrency of WorkMethod(i, j)? Is it 4, 8, or 16?
ParallelOptions.MaxDegreeOfParallelism is not applied globally. If you have enough cores, and the scheduler sees fit you will get a multiplication of the nested MPD values with each For able to spin up that many tasks (if the workloads are unconstrained).
Consider this example, 3 tasks can start 3 more tasks. This is limited by the MDP option of 3.
int k = 0;
ParallelOptions po = new ParallelOptions();
po.MaxDegreeOfParallelism = 3;
Parallel.For(0, 10, po, (i) =>
{
Parallel.For(0, 10, po, (j) =>
{
Interlocked.Increment(ref k);
Console.WriteLine(k);
Thread.Sleep(2000);
Interlocked.Decrement(ref k);
});
Thread.Sleep(2000);
});
Output
1
2
3
4
7
5
6
8
9
9
5
6
7
9
9
8
8
9
...
If MDP was global you would only get 3 I guess, since it's not you get 9s.
ParallelOptions.MaxDegreeOfParallelism is not global, it is per parallel loop. And more specifically, it sets the max number of tasks that can run in parallel, not the max number of cores or threads that will run those tasks in parallel.
Some demo tests
note: i have 4 cores, 8 threads
What's happening in the code
We're running 2 async methods; each one kicks off nested parallel loops.
We're setting max degrees of parallelism to 2 and a sleep time of 2 seconds to simulate the work each task does
So, due to setting MaxDegreeOfParallelism to 2, we would expect to reach up to 12 concurrent tasks before the 40 tasks complete (i'm only counting tasks kicked off by the nested parallel loops)
how do i get 12?
2 max concurrent tasks started in the outer loop
+4 max concurrent tasks from inner loop (2 started per task started in outer loop)
that's 6 (per asynchronous task kicked off in Main)
12 total
test code
using System;
using System.Threading;
using System.Threading.Tasks;
namespace forfun
{
class Program
{
static void Main(string[] args)
{
var taskRunner = new TaskRunner();
taskRunner.RunTheseTasks();
taskRunner.RunTheseTasksToo();
Console.ReadLine();
}
private class TaskRunner
{
private int _totalTasks = 0;
private int _runningTasks = 0;
public async void RunTheseTasks()
{
await Task.Run(() => ProcessThingsInParallel());
}
public async void RunTheseTasksToo()
{
await Task.Run(() => ProcessThingsInParallel());
}
private void ProcessThingsInParallel()
{
ParallelOptions po = new ParallelOptions();
po.MaxDegreeOfParallelism = 2;
Parallel.For(0, 4, po, (i) =>
{
Interlocked.Increment(ref _totalTasks);
Interlocked.Increment(ref _runningTasks);
Console.WriteLine($"{_runningTasks} currently running of {_totalTasks} total tasks");
Parallel.For(0, 4, po, (j) =>
{
Interlocked.Increment(ref _totalTasks);
Interlocked.Increment(ref _runningTasks);
Console.WriteLine($"{_runningTasks} currently running of {_totalTasks} total tasks");
WorkMethod(i, j); // assume a long-running method
Interlocked.Decrement(ref _runningTasks);
});
Interlocked.Decrement(ref _runningTasks);
}
);
}
private static void WorkMethod(int i, int l)
{
Thread.Sleep(2000);
}
}
}
}
Spoiler, the output shows that setting MaxDegreeOfParallelism is not global, is not limited to core or thread count, and is specifically setting a max on concurrent running tasks.
output with max set to 2:
1 currently running of 1 total tasks
3 currently running of 3 total tasks
2 currently running of 2 total tasks
4 currently running of 4 total tasks
5 currently running of 5 total tasks
7 currently running of 7 total tasks
[ ... snip ...]
11 currently running of 33 total tasks
12 currently running of 34 total tasks
11 currently running of 35 total tasks
12 currently running of 36 total tasks
11 currently running of 37 total tasks
12 currently running of 38 total tasks
11 currently running of 39 total tasks
12 currently running of 40 total tasks
(output will vary, but each time, the max concurrent should be 12)
output without max set:
1 currently running of 1 total tasks
3 currently running of 3 total tasks
4 currently running of 4 total tasks
2 currently running of 2 total tasks
5 currently running of 5 total tasks
7 currently running of 7 total tasks
[ ... snip ...]
19 currently running of 28 total tasks
19 currently running of 29 total tasks
18 currently running of 30 total tasks
13 currently running of 31 total tasks
13 currently running of 32 total tasks
16 currently running of 35 total tasks
16 currently running of 36 total tasks
14 currently running of 33 total tasks
15 currently running of 34 total tasks
15 currently running of 37 total tasks
16 currently running of 38 total tasks
16 currently running of 39 total tasks
17 currently running of 40 total tasks
notice how without setting the max, we get up to 19 concurrent tasks
- now the 2 second sleep time is limiting the number of tasks that could kick off before others finished
output after increasing sleep time to 12 seconds
1 currently running of 1 total tasks
2 currently running of 2 total tasks
3 currently running of 3 total tasks
4 currently running of 4 total tasks
[ ... snip ...]
26 currently running of 34 total tasks
26 currently running of 35 total tasks
27 currently running of 36 total tasks
28 currently running of 37 total tasks
28 currently running of 38 total tasks
28 currently running of 39 total tasks
28 currently running of 40 total tasks
got up to 28 concurrent tasks
now setting loops to 10 nested in 10 and setting sleep time back to 2 seconds - again no max set
1 currently running of 1 total tasks
3 currently running of 3 total tasks
2 currently running of 2 total tasks
4 currently running of 4 total tasks
[ ... snip ...]
38 currently running of 176 total tasks
38 currently running of 177 total tasks
38 currently running of 178 total tasks
37 currently running of 179 total tasks
38 currently running of 180 total tasks
38 currently running of 181 total tasks
[ ... snip ...]
35 currently running of 216 total tasks
35 currently running of 217 total tasks
32 currently running of 218 total tasks
32 currently running of 219 total tasks
33 currently running of 220 total tasks
got up to 38 concurrent tasks before all 220 finished
More related information
ParallelOptions.MaxDegreeOfParallelism Property
The MaxDegreeOfParallelism property affects the number of concurrent operations run by Parallel method calls that are passed this ParallelOptions instance. A positive property value limits the number of concurrent operations to the set value. If it is -1, there is no limit on the number of concurrently running operations.
By default, For and ForEach will utilize however many threads the underlying scheduler provides, so changing MaxDegreeOfParallelism from the default only limits how many concurrent tasks will be used.
to get the max degree of parallelism, don't set it, rather allow the TPL and its scheduler handle it
setting the max degree of parallelism only affects the number of concurrent tasks, not threads used
the maximum number of concurrent tasks is not equal to the number of threads available--threads will still be able to juggle multiple tasks; and even if your app is using all threads, it is still sharing those threads with the other processes that the machine is hosting
Environment.ProcessorCount
Gets the number of processors on the current machine.
What if we say MaxDegreeOfParallelism = Environment.ProcessorCount?
Even setting max degree of parallism to Environment.ProcessorCount does not dynamically ensure that you get the maximum concurrency regardless of the system your app is running on. Doing this still limits the degree of parallelism, because any given thread can switch between many tasks--so this would just limit the number of concurrent tasks to equal the number of available threads--and this does not necessarily mean that each concurrent task will be assigned neatly to each thread in a one-to-one relationship.
I am working a contract right now to increase the performance of a back end services for a modern SaaS SPA web app that is using EF 6 as their ORM. The first thing I proposed was to introduce some multi-threading to their back end service which is currently running single threaded. The lead software engineer has indicated we cannot do that because EF 6 is not thread-safe.
I am no expert on Entity Framework. My ORM of choice is XPO by DevExpress and I have done something similar to what is proposed below without issue using that ORM. Is this pattern inherently not safe using EF 6?
int[] ids;
using(var db = new ApplicationDbContext())
{
// query to surface id's of records representing work to be done
ids = GetIdsOfRecordsRepresentingSomeTask(db);
}
Parallel.ForEach(ids, id => {
using(var db = new ApplicationDbContext())
{
var processor = new SomeTaskProcessor(db, id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
db.SaveChanges();
}
});
I have researched this, and I agree that DbContext is not thread-safe. The pattern I propose does use multiple threads, but a single DbContext is only every accessed by a single thread in a single-threaded fashion. The lead is telling me that DbContext is essentially a singleton under the covers and this code would ultimately mess up the database. I can't find anything to support this claim. Is the lead right on this?
Thanks
Your pattern is thread-safe. However, at least for SQL Server, if your concurrency is too high, you'll find that your total throughput drops off as contention for database resources increases.
In theory, Parallel.ForEach optimizes the number of threads, but in practice, I have found it allows too much concurrency in my applications.
You can control concurrency with the ParallelOptions optional parameter. Test your use case and see if the default concurrency works well for you.
Your comment: Keeping in mind that, right now anyway, that we are talking about 100's of ids in that code above where most id's represent work that does not end up in any changes to the database and are short lived while a hand full can take minutes to complete and ultimately add 10's of new records to the DB. What MaxDegreesOfParallelism value would you recommend off the top of your head?
Probably 2-3 based on your general description, but it depends on how database intensive ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords is (vs. performing CPU-bound activities or waiting for IO from files, web service calls, etc). With more than that, if that method is mostly performing DB tasks, you're likely to get locking contention or overwhelm your IO subsystem (disks). Test in your environment to be sure.
It may be worth exploring why ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords is taking so long to complete for a given Id.
UPDATE
Here's some test code to demonstrate that the threads do not block each other and indeed run concurrently. I removed the DbContext portion for simplicity and since it doesn't affect the threading issue.
class SomeTaskProcessor
{
static Random rng = new Random();
public int Id { get; private set; }
public SomeTaskProcessor(int id) { Id = id; }
public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords()
{
Console.WriteLine($"Starting ID {Id}");
System.Threading.Thread.Sleep(rng.Next(1000));
Console.WriteLine($"Completing ID {Id}");
}
}
class Program
{
static void Main(string[] args)
{
int[] ids = Enumerable.Range(1, 100).ToArray();
Parallel.ForEach(ids, id => {
var processor = new SomeTaskProcessor(id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
});
}
}
Issue #1 - Memory Allocation
Every single iteration will create instance of DbContext which will lead to memory allocations. Garbage collector will need to deal with those allocations. If there will be constant memory pressure it will end up in performance degradation on application level.
Issue #2 - SQL overload
Based on written above during every single iteration you'll call SaveChanges() that will probably make a call to database. In case call to the database is/will be resource intensive you may end up with poorly performing database.
Issue #3 - Thread blocking
SaveChanges() is synchronous and will block the thread. In situation call to database takes significant amount of time, thread will just sit and wait. Same for 3rd party API calls. There will be no or small performance gain.
Issue #4 - Parallel.For* methods are not guaranteed to run in parallel
It is important to keep in mind that individual iterations in a Parallel.For, Parallel.ForEach or ForAll loop may but do not have to execute in parallel.
I think this is self explanatory.
Potential Pitfalls with PLINQ
Understanding Speedup in PLINQ
I am reading comments under the question and Eric J. answer I am afraid there is problem with approach to the problem and parallelism is not going to help.
Thanks for the answer Eric. Keeping in mind that, right now anyway, that we are talking about 100's of ids in that code above where most id's represent work that does not end up in any changes to the database and are short lived while a hand full can take minutes to complete and ultimately add 10's of new records to the DB. What MaxDegreesOfParallelism value would you recommend off the top of your head?
Ahh - yes I should have included that detail. In this instance the task being performed is very much IO bound calling out to third party web API to collect required information for processing. Once all the data is collected, it is processed and may result in some new records needing to be added to the database. There are a few queries to collect some additional information during the processing phase, but all those are performing fine.
If I am not completely wrong the problem are calls to 3rd party API synchronously, which blocks the thread.
I think async approach might help here to gather data from 3rd party API.
Also would help not allocating heap memory if not necessary. You can save changes at the end of execution with just one DbContext instance and just one database call.
UPDATE
Eric J. added test to test threads are not blocking each other(which they don't) and to test concurrent execution. Code below is his original code provided in his answer.
class SomeTaskProcessor
{
static Random rng = new Random();
public int Id { get; private set; }
public SomeTaskProcessor(int id) { Id = id; }
public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords()
{
Console.WriteLine($"Starting ID {Id}");
System.Threading.Thread.Sleep(rng.Next(1000));
Console.WriteLine($"Completing ID {Id}");
}
}
class Program
{
static void Main(string[] args)
{
int[] ids = Enumerable.Range(1, 100).ToArray();
Parallel.ForEach(ids, id => {
var processor = new SomeTaskProcessor(id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
});
}
}
I made similar test. I created server to simulate internet IO with delayed response of 1 second. Before each test run API server was started and first request made. Test were run in Release config.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase {
// GET api/values
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> Get() {
await Task.Delay(1000);
return new string[] { "value1", "value2" };
}
}
I have updated his code to call API endpoint. On my virtual machine it spun 5 thread and was executing on average 23 seconds.
class SomeTaskProcessor {
private HttpClient http = new HttpClient() {
BaseAddress = new Uri("http://localhost:49890/api/values")
};
public int Id { get; private set; }
public SomeTaskProcessor(int id) { Id = id; }
public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords() {
Console.WriteLine($"Starting ID {Id}. Thread Id: {Thread.CurrentThread.ManagedThreadId}");
var response = http.GetAsync(String.Empty).GetAwaiter().GetResult();
Console.WriteLine($"Completing ID {Id}. Response status code is {response.StatusCode}. Thread Id: {Thread.CurrentThread.ManagedThreadId}");
}
}
class Program {
static void Main(string[] args) {
int[] ids = Enumerable.Range(1, 100).ToArray();;
var stopwatch = new Stopwatch();
stopwatch.Start();
Parallel.ForEach(ids, id => {
var processor = new SomeTaskProcessor(id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
});
// ~23 seconds
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed.ToString());
}
}
Console output:
Starting ID 51. Thread Id: 4
Starting ID 1. Thread Id: 1
Starting ID 2. Thread Id: 5
Starting ID 52. Thread Id: 9
Starting ID 3. Thread Id: 12
Completing ID 51. Response status code is OK. Thread Id: 4
Starting ID 53. Thread Id: 4
Completing ID 2. Response status code is OK. Thread Id: 5
Starting ID 4. Thread Id: 5
Starting ID 55. Thread Id: 14
Starting ID 6. Thread Id: 15
Completing ID 52. Response status code is OK. Thread Id: 9
Starting ID 56. Thread Id: 9
Completing ID 1. Response status code is OK. Thread Id: 1
Starting ID 7. Thread Id: 1
Completing ID 3. Response status code is OK. Thread Id: 12
Starting ID 9. Thread Id: 12
Starting ID 58. Thread Id: 16
Completing ID 53. Response status code is OK. Thread Id: 4
Starting ID 54. Thread Id: 4
Completing ID 4. Response status code is OK. Thread Id: 5
Starting ID 5. Thread Id: 5
Starting ID 11. Thread Id: 18
Completing ID 55. Response status code is OK. Thread Id: 14
Starting ID 59. Thread Id: 14
Starting ID 61. Thread Id: 20
Completing ID 56. Response status code is OK. Thread Id: 9
Starting ID 57. Thread Id: 9
Completing ID 7. Response status code is OK. Thread Id: 1
Starting ID 8. Thread Id: 1
Completing ID 9. Response status code is OK. Thread Id: 12
Starting ID 10. Thread Id: 12
Completing ID 6. Response status code is OK. Thread Id: 15
Starting ID 12. Thread Id: 15
Starting ID 14. Thread Id: 22
Completing ID 5. Response status code is OK. Thread Id: 5
Starting ID 15. Thread Id: 5
Completing ID 54. Response status code is OK. Thread Id: 4
Starting ID 62. Thread Id: 4
Completing ID 58. Response status code is OK. Thread Id: 16
Starting ID 66. Thread Id: 16
Starting ID 68. Thread Id: 23
Completing ID 11. Response status code is OK. Thread Id: 18
Starting ID 19. Thread Id: 18
Completing ID 59. Response status code is OK. Thread Id: 14
Starting ID 60. Thread Id: 14
Starting ID 21. Thread Id: 24
Completing ID 57. Response status code is OK. Thread Id: 9
Starting ID 69. Thread Id: 9
Completing ID 12. Response status code is OK. Thread Id: 15
Starting ID 13. Thread Id: 15
Completing ID 61. Response status code is OK. Thread Id: 20
Starting ID 73. Thread Id: 20
Completing ID 10. Response status code is OK. Thread Id: 12
Completing ID 8. Response status code is OK. Thread Id: 1
Starting ID 22. Thread Id: 12
Starting ID 26. Thread Id: 1
Starting ID 75. Thread Id: 25
Completing ID 15. Response status code is OK. Thread Id: 5
Starting ID 16. Thread Id: 5
Completing ID 62. Response status code is OK. Thread Id: 4
Starting ID 63. Thread Id: 4
Completing ID 66. Response status code is OK. Thread Id: 16
Starting ID 67. Thread Id: 16
Completing ID 14. Response status code is OK. Thread Id: 22
Starting ID 30. Thread Id: 22
Starting ID 32. Thread Id: 26
Starting ID 76. Thread Id: 27
Completing ID 60. Response status code is OK. Thread Id: 14
Starting ID 77. Thread Id: 14
Completing ID 19. Response status code is OK. Thread Id: 18
Starting ID 20. Thread Id: 18
Completing ID 69. Response status code is OK. Thread Id: 9
Starting ID 70. Thread Id: 9
Completing ID 21. Response status code is OK. Thread Id: 24
Starting ID 33. Thread Id: 24
Completing ID 13. Response status code is OK. Thread Id: 15
Starting ID 35. Thread Id: 15
Completing ID 73. Response status code is OK. Thread Id: 20
Starting ID 74. Thread Id: 20
Completing ID 22. Response status code is OK. Thread Id: 12
Starting ID 23. Thread Id: 12
Completing ID 26. Response status code is OK. Thread Id: 1
Starting ID 27. Thread Id: 1
Completing ID 68. Response status code is OK. Thread Id: 23
Starting ID 81. Thread Id: 23
Starting ID 39. Thread Id: 29
Completing ID 67. Response status code is OK. Thread Id: 16
Starting ID 83. Thread Id: 16
Completing ID 75. Response status code is OK. Thread Id: 25
Completing ID 30. Response status code is OK. Thread Id: 22
Starting ID 31. Thread Id: 22
Completing ID 16. Response status code is OK. Thread Id: 5
Starting ID 87. Thread Id: 25
Starting ID 17. Thread Id: 5
Completing ID 63. Response status code is OK. Thread Id: 4
Starting ID 64. Thread Id: 4
Starting ID 89. Thread Id: 30
Completing ID 76. Response status code is OK. Thread Id: 27
Starting ID 90. Thread Id: 27
Completing ID 32. Response status code is OK. Thread Id: 26
Starting ID 40. Thread Id: 26
Completing ID 77. Response status code is OK. Thread Id: 14
Starting ID 78. Thread Id: 14
Starting ID 42. Thread Id: 31
Completing ID 70. Response status code is OK. Thread Id: 9
Starting ID 71. Thread Id: 9
Completing ID 27. Response status code is OK. Thread Id: 1
Starting ID 28. Thread Id: 1
Completing ID 74. Response status code is OK. Thread Id: 20
Starting ID 92. Thread Id: 20
Completing ID 35. Response status code is OK. Thread Id: 15
Starting ID 36. Thread Id: 15
Completing ID 81. Response status code is OK. Thread Id: 23
Starting ID 82. Thread Id: 23
Completing ID 33. Response status code is OK. Thread Id: 24
Starting ID 34. Thread Id: 24
Completing ID 20. Response status code is OK. Thread Id: 18
Starting ID 43. Thread Id: 18
Completing ID 23. Response status code is OK. Thread Id: 12
Starting ID 24. Thread Id: 12
Starting ID 96. Thread Id: 32
Completing ID 39. Response status code is OK. Thread Id: 29
Completing ID 17. Response status code is OK. Thread Id: 5
Starting ID 18. Thread Id: 5
Starting ID 47. Thread Id: 29
Completing ID 64. Response status code is OK. Thread Id: 4
Starting ID 65. Thread Id: 4
Completing ID 87. Response status code is OK. Thread Id: 25
Starting ID 88. Thread Id: 25
Completing ID 31. Response status code is OK. Thread Id: 22
Completing ID 83. Response status code is OK. Thread Id: 16
Starting ID 84. Thread Id: 16
Starting ID 49. Thread Id: 22
Starting ID 97. Thread Id: 33
Completing ID 90. Response status code is OK. Thread Id: 27
Starting ID 91. Thread Id: 27
Completing ID 40. Response status code is OK. Thread Id: 26
Starting ID 41. Thread Id: 26
Completing ID 89. Response status code is OK. Thread Id: 30
Starting ID 98. Thread Id: 30
Completing ID 78. Response status code is OK. Thread Id: 14
Starting ID 79. Thread Id: 14
Starting ID 100. Thread Id: 34
Completing ID 36. Response status code is OK. Thread Id: 15
Starting ID 37. Thread Id: 15
Completing ID 92. Response status code is OK. Thread Id: 20
Starting ID 93. Thread Id: 20
Completing ID 42. Response status code is OK. Thread Id: 31
Completing ID 28. Response status code is OK. Thread Id: 1
Starting ID 29. Thread Id: 1
Completing ID 24. Response status code is OK. Thread Id: 12
Starting ID 25. Thread Id: 12
Completing ID 82. Response status code is OK. Thread Id: 23
Completing ID 71. Response status code is OK. Thread Id: 9
Starting ID 72. Thread Id: 9
Completing ID 43. Response status code is OK. Thread Id: 18
Starting ID 44. Thread Id: 18
Completing ID 96. Response status code is OK. Thread Id: 32
Completing ID 65. Response status code is OK. Thread Id: 4
Completing ID 88. Response status code is OK. Thread Id: 25
Completing ID 49. Response status code is OK. Thread Id: 22
Starting ID 50. Thread Id: 22
Completing ID 47. Response status code is OK. Thread Id: 29
Starting ID 48. Thread Id: 29
Completing ID 18. Response status code is OK. Thread Id: 5
Completing ID 34. Response status code is OK. Thread Id: 24
Completing ID 84. Response status code is OK. Thread Id: 16
Starting ID 85. Thread Id: 16
Completing ID 97. Response status code is OK. Thread Id: 33
Completing ID 41. Response status code is OK. Thread Id: 26
Completing ID 79. Response status code is OK. Thread Id: 14
Starting ID 80. Thread Id: 14
Completing ID 100. Response status code is OK. Thread Id: 34
Completing ID 93. Response status code is OK. Thread Id: 20
Starting ID 94. Thread Id: 20
Completing ID 29. Response status code is OK. Thread Id: 1
Completing ID 98. Response status code is OK. Thread Id: 30
Starting ID 99. Thread Id: 30
Completing ID 44. Response status code is OK. Thread Id: 18
Starting ID 45. Thread Id: 18
Completing ID 25. Response status code is OK. Thread Id: 12
Completing ID 37. Response status code is OK. Thread Id: 15
Starting ID 38. Thread Id: 15
Completing ID 72. Response status code is OK. Thread Id: 9
Completing ID 91. Response status code is OK. Thread Id: 27
Completing ID 50. Response status code is OK. Thread Id: 22
Completing ID 48. Response status code is OK. Thread Id: 29
Completing ID 85. Response status code is OK. Thread Id: 16
Starting ID 86. Thread Id: 16
Completing ID 80. Response status code is OK. Thread Id: 14
Completing ID 94. Response status code is OK. Thread Id: 20
Starting ID 95. Thread Id: 20
Completing ID 99. Response status code is OK. Thread Id: 30
Completing ID 45. Response status code is OK. Thread Id: 18
Starting ID 46. Thread Id: 18
Completing ID 38. Response status code is OK. Thread Id: 15
Completing ID 86. Response status code is OK. Thread Id: 16
Completing ID 95. Response status code is OK. Thread Id: 20
Completing ID 46. Response status code is OK. Thread Id: 18
00:00:23.6046580
C:\Program Files\dotnet\dotnet.exe (process 7208) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
Then I created async version of the same code. Execition took on average 7 seconds.
class SomeTaskProcessor {
private HttpClient http = new HttpClient() {
BaseAddress = new Uri("http://localhost:49890/api/values")
};
public int Id { get; private set; }
public SomeTaskProcessor(int id) { Id = id; }
public async Task ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords() {
Console.WriteLine($"Starting ID {Id}");
var response = await http.GetAsync(String.Empty);
Console.WriteLine($"Completing ID {Id}. Response status code is {response.StatusCode}");
}
}
class Program {
static async Task Main(string[] args) {
int[] ids = Enumerable.Range(1, 100).ToArray();;
var stopwatch = new Stopwatch();
stopwatch.Start();
var tasks = ids.Select(id => {
var processor = new SomeTaskProcessor(id);
return processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
}).ToArray();
await Task.WhenAll(tasks);
// ~8 seconds
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed.ToString());
}
}
Console output:
Starting ID 1. Thread Id: 1
Starting ID 2. Thread Id: 1
Starting ID 3. Thread Id: 1
Starting ID 4. Thread Id: 1
Starting ID 5. Thread Id: 1
Starting ID 6. Thread Id: 1
Starting ID 7. Thread Id: 1
Starting ID 8. Thread Id: 1
Starting ID 9. Thread Id: 1
Starting ID 10. Thread Id: 1
Starting ID 11. Thread Id: 1
Starting ID 12. Thread Id: 1
Starting ID 13. Thread Id: 1
Starting ID 14. Thread Id: 1
Starting ID 15. Thread Id: 1
Starting ID 16. Thread Id: 1
Starting ID 17. Thread Id: 1
Starting ID 18. Thread Id: 1
Starting ID 19. Thread Id: 1
Starting ID 20. Thread Id: 1
Starting ID 21. Thread Id: 1
Starting ID 22. Thread Id: 1
Starting ID 23. Thread Id: 1
Starting ID 24. Thread Id: 1
Starting ID 25. Thread Id: 1
Starting ID 26. Thread Id: 1
Starting ID 27. Thread Id: 1
Starting ID 28. Thread Id: 1
Starting ID 29. Thread Id: 1
Starting ID 30. Thread Id: 1
Starting ID 31. Thread Id: 1
Starting ID 32. Thread Id: 1
Starting ID 33. Thread Id: 1
Starting ID 34. Thread Id: 1
Starting ID 35. Thread Id: 1
Starting ID 36. Thread Id: 1
Starting ID 37. Thread Id: 1
Starting ID 38. Thread Id: 1
Starting ID 39. Thread Id: 1
Starting ID 40. Thread Id: 1
Starting ID 41. Thread Id: 1
Starting ID 42. Thread Id: 1
Starting ID 43. Thread Id: 1
Starting ID 44. Thread Id: 1
Starting ID 45. Thread Id: 1
Starting ID 46. Thread Id: 1
Starting ID 47. Thread Id: 1
Starting ID 48. Thread Id: 1
Starting ID 49. Thread Id: 1
Starting ID 50. Thread Id: 1
Starting ID 51. Thread Id: 1
Starting ID 52. Thread Id: 1
Starting ID 53. Thread Id: 1
Starting ID 54. Thread Id: 1
Starting ID 55. Thread Id: 1
Starting ID 56. Thread Id: 1
Starting ID 57. Thread Id: 1
Starting ID 58. Thread Id: 1
Starting ID 59. Thread Id: 1
Starting ID 60. Thread Id: 1
Starting ID 61. Thread Id: 1
Starting ID 62. Thread Id: 1
Starting ID 63. Thread Id: 1
Starting ID 64. Thread Id: 1
Starting ID 65. Thread Id: 1
Starting ID 66. Thread Id: 1
Starting ID 67. Thread Id: 1
Starting ID 68. Thread Id: 1
Starting ID 69. Thread Id: 1
Starting ID 70. Thread Id: 1
Starting ID 71. Thread Id: 1
Starting ID 72. Thread Id: 1
Starting ID 73. Thread Id: 1
Starting ID 74. Thread Id: 1
Starting ID 75. Thread Id: 1
Starting ID 76. Thread Id: 1
Starting ID 77. Thread Id: 1
Starting ID 78. Thread Id: 1
Starting ID 79. Thread Id: 1
Starting ID 80. Thread Id: 1
Starting ID 81. Thread Id: 1
Starting ID 82. Thread Id: 1
Starting ID 83. Thread Id: 1
Starting ID 84. Thread Id: 1
Starting ID 85. Thread Id: 1
Starting ID 86. Thread Id: 1
Starting ID 87. Thread Id: 1
Starting ID 88. Thread Id: 1
Starting ID 89. Thread Id: 1
Starting ID 90. Thread Id: 1
Starting ID 91. Thread Id: 1
Starting ID 92. Thread Id: 1
Starting ID 93. Thread Id: 1
Starting ID 94. Thread Id: 1
Starting ID 95. Thread Id: 1
Starting ID 96. Thread Id: 1
Starting ID 97. Thread Id: 1
Starting ID 98. Thread Id: 1
Starting ID 99. Thread Id: 1
Starting ID 100. Thread Id: 1
Completing ID 3. Response status code is OK. Thread Id: 14
Completing ID 10. Response status code is OK. Thread Id: 8
Completing ID 8. Response status code is OK. Thread Id: 9
Completing ID 7. Response status code is OK. Thread Id: 15
Completing ID 11. Response status code is OK. Thread Id: 14
Completing ID 4. Response status code is OK. Thread Id: 8
Completing ID 9. Response status code is OK. Thread Id: 9
Completing ID 12. Response status code is OK. Thread Id: 8
Completing ID 13. Response status code is OK. Thread Id: 8
Completing ID 6. Response status code is OK. Thread Id: 8
Completing ID 17. Response status code is OK. Thread Id: 8
Completing ID 18. Response status code is OK. Thread Id: 8
Completing ID 21. Response status code is OK. Thread Id: 8
Completing ID 24. Response status code is OK. Thread Id: 8
Completing ID 20. Response status code is OK. Thread Id: 8
Completing ID 30. Response status code is OK. Thread Id: 8
Completing ID 22. Response status code is OK. Thread Id: 8
Completing ID 34. Response status code is OK. Thread Id: 8
Completing ID 32. Response status code is OK. Thread Id: 9
Completing ID 33. Response status code is OK. Thread Id: 9
Completing ID 39. Response status code is OK. Thread Id: 9
Completing ID 35. Response status code is OK. Thread Id: 8
Completing ID 2. Response status code is OK. Thread Id: 9
Completing ID 44. Response status code is OK. Thread Id: 9
Completing ID 23. Response status code is OK. Thread Id: 8
Completing ID 31. Response status code is OK. Thread Id: 14
Completing ID 38. Response status code is OK. Thread Id: 14
Completing ID 43. Response status code is OK. Thread Id: 8
Completing ID 50. Response status code is OK. Thread Id: 9
Completing ID 1. Response status code is OK. Thread Id: 15
Completing ID 48. Response status code is OK. Thread Id: 14
Completing ID 27. Response status code is OK. Thread Id: 8
Completing ID 49. Response status code is OK. Thread Id: 9
Completing ID 28. Response status code is OK. Thread Id: 15
Completing ID 14. Response status code is OK. Thread Id: 14
Completing ID 29. Response status code is OK. Thread Id: 8
Completing ID 26. Response status code is OK. Thread Id: 14
Completing ID 15. Response status code is OK. Thread Id: 9
Completing ID 19. Response status code is OK. Thread Id: 8
Completing ID 25. Response status code is OK. Thread Id: 15
Completing ID 5. Response status code is OK. Thread Id: 14
Completing ID 40. Response status code is OK. Thread Id: 9
Completing ID 60. Response status code is OK. Thread Id: 8
Completing ID 37. Response status code is OK. Thread Id: 15
Completing ID 41. Response status code is OK. Thread Id: 14
Completing ID 16. Response status code is OK. Thread Id: 9
Completing ID 63. Response status code is OK. Thread Id: 14
Completing ID 36. Response status code is OK. Thread Id: 14
Completing ID 42. Response status code is OK. Thread Id: 9
Completing ID 45. Response status code is OK. Thread Id: 14
Completing ID 64. Response status code is OK. Thread Id: 14
Completing ID 53. Response status code is OK. Thread Id: 9
Completing ID 61. Response status code is OK. Thread Id: 15
Completing ID 52. Response status code is OK. Thread Id: 14
Completing ID 67. Response status code is OK. Thread Id: 14
Completing ID 74. Response status code is OK. Thread Id: 14
Completing ID 75. Response status code is OK. Thread Id: 14
Completing ID 62. Response status code is OK. Thread Id: 15
Completing ID 78. Response status code is OK. Thread Id: 8
Completing ID 66. Response status code is OK. Thread Id: 15
Completing ID 55. Response status code is OK. Thread Id: 14
Completing ID 83. Response status code is OK. Thread Id: 15
Completing ID 59. Response status code is OK. Thread Id: 14
Completing ID 68. Response status code is OK. Thread Id: 8
Completing ID 85. Response status code is OK. Thread Id: 15
Completing ID 47. Response status code is OK. Thread Id: 9
Completing ID 72. Response status code is OK. Thread Id: 14
Completing ID 65. Response status code is OK. Thread Id: 8
Completing ID 84. Response status code is OK. Thread Id: 8
Completing ID 70. Response status code is OK. Thread Id: 14
Completing ID 87. Response status code is OK. Thread Id: 14
Completing ID 56. Response status code is OK. Thread Id: 8
Completing ID 90. Response status code is OK. Thread Id: 15
Completing ID 76. Response status code is OK. Thread Id: 9
Completing ID 73. Response status code is OK. Thread Id: 14
Completing ID 69. Response status code is OK. Thread Id: 8
Completing ID 86. Response status code is OK. Thread Id: 15
Completing ID 81. Response status code is OK. Thread Id: 9
Completing ID 91. Response status code is OK. Thread Id: 15
Completing ID 77. Response status code is OK. Thread Id: 9
Completing ID 57. Response status code is OK. Thread Id: 15
Completing ID 98. Response status code is OK. Thread Id: 9
Completing ID 100. Response status code is OK. Thread Id: 15
Completing ID 79. Response status code is OK. Thread Id: 9
Completing ID 58. Response status code is OK. Thread Id: 15
Completing ID 80. Response status code is OK. Thread Id: 8
Completing ID 82. Response status code is OK. Thread Id: 14
Completing ID 89. Response status code is OK. Thread Id: 9
Completing ID 88. Response status code is OK. Thread Id: 15
Completing ID 97. Response status code is OK. Thread Id: 8
Completing ID 51. Response status code is OK. Thread Id: 14
Completing ID 94. Response status code is OK. Thread Id: 9
Completing ID 93. Response status code is OK. Thread Id: 15
Completing ID 71. Response status code is OK. Thread Id: 8
Completing ID 46. Response status code is OK. Thread Id: 14
Completing ID 54. Response status code is OK. Thread Id: 9
Completing ID 95. Response status code is OK. Thread Id: 15
Completing ID 92. Response status code is OK. Thread Id: 8
Completing ID 99. Response status code is OK. Thread Id: 14
Completing ID 96. Response status code is OK. Thread Id: 9
00:00:06.7737961
C:\Program Files\dotnet\dotnet.exe (process 296) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
I believe 23 seconds vs 7 seconds are enough of proof to say it is async/await scenario.
I am working on a console application that sends multiple requests to an API and I am making use of async, tasks and await. I am using the Stopwatch to show the time spent for each request/task and I noticed that it starts very low (150 ms) and there is adding around ~100 ms for each next task.
I think the tasks are running concurrently because the program completes 83 requests/tasks in 8 seconds and when I measure the get request with Chrome it showed around 200ms.
Do you know why the time is increasing as the tasks go? Is there something wrong with the measuring or with my code logic?
Isn't this suppose to be faster? From what I red, WhenAll should run the tasks concurrently and the overall completion time is the max task time from the list.
public async Task<List<CatalogEvent>> GetEventsAsync(int id)
{
sw.Restart();
var request = GetRequest(msCatalogEndpoint);
request.AddParameter("id", id, ParameterType.UrlSegment);
List<CatalogEvent> events = new List<CatalogEvent>();
var response = await client.ExecuteTaskAsync(request).ConfigureAwait(false);
var catalog = JsonConvert.DeserializeObject<CatalogEndpoint>(response.Content);
if (!(catalog.catalogEvents is null))
{
foreach (var ev in catalog.catalogEvents)
{
CatalogEvent catalogEvent = ev.Value;
catalogEvent.eventName = ev.Key.ToString();
catalogEvent.titleId = id;
DateTime dateTime = DateTime.UtcNow;
catalogEvent.date = dateTime.ToString();
events.Add(catalogEvent);
}
}
Console.WriteLine($"Task for Id: {id} took {sw.ElapsedMilliseconds} ms and was managed by Thread: {Thread.CurrentThread.ManagedThreadId}");
return events;
}
I am using RestSharp package to make the requests.
The main method is like this:
static void Main(string[] args)
{
//this list has 83 ids which I am getting from a database
List<int> ids = GetIds();
async Task ProcessEvents()
{
IEnumerable<Task<List<CatalogEvent>>> techBriefEvents = ids.Select(id => GetEventsAsync(id));
await Task.WhenAll(techBriefEvents);
}
Task.WhenAll(ProcessEvents());
Console.ReadKey();
}
This is the output:
Task for TitleId: 142 took 164 ms and was managed by Thread: 8
Task for TitleId: 16 took 349 ms and was managed by Thread: 5
Task for TitleId: 10 took 634 ms and was managed by Thread: 6
Task for TitleId: 215 took 650 ms and was managed by Thread: 5
Task for TitleId: 114 took 826 ms and was managed by Thread: 6
Task for TitleId: 214 took 843 ms and was managed by Thread: 5
Task for TitleId: 56 took 983 ms and was managed by Thread: 6
Task for TitleId: 212 took 1001 ms and was managed by Thread: 5
Task for TitleId: 168 took 1141 ms and was managed by Thread: 6
Task for TitleId: 21 took 1168 ms and was managed by Thread: 5
Task for TitleId: 26 took 1309 ms and was managed by Thread: 6
Task for TitleId: 30 took 1334 ms and was managed by Thread: 5
Task for TitleId: 213 took 1462 ms and was managed by Thread: 6
Task for TitleId: 24 took 1510 ms and was managed by Thread: 5
Task for TitleId: 29 took 1619 ms and was managed by Thread: 6
Task for TitleId: 23 took 1669 ms and was managed by Thread: 5
Task for TitleId: 31 took 1779 ms and was managed by Thread: 6
Task for TitleId: 14 took 1906 ms and was managed by Thread: 5
Task for TitleId: 18 took 1943 ms and was managed by Thread: 6
Task for TitleId: 20 took 2064 ms and was managed by Thread: 6
Task for TitleId: 19 took 2110 ms and was managed by Thread: 6
Task for TitleId: 175 took 2222 ms and was managed by Thread: 8
Task for TitleId: 15 took 2275 ms and was managed by Thread: 6
Task for TitleId: 102 took 2400 ms and was managed by Thread: 8
Task for TitleId: 33 took 2464 ms and was managed by Thread: 8
Task for TitleId: 135 took 2563 ms and was managed by Thread: 5
Task for TitleId: 5 took 2632 ms and was managed by Thread: 8
Task for TitleId: 137 took 2750 ms and was managed by Thread: 5
Task for TitleId: 12 took 2796 ms and was managed by Thread: 8
Task for TitleId: 41 took 2911 ms and was managed by Thread: 5
Task for TitleId: 136 took 2998 ms and was managed by Thread: 8
Task for TitleId: 43 took 3084 ms and was managed by Thread: 5
Task for TitleId: 139 took 3159 ms and was managed by Thread: 8
Task for TitleId: 51 took 3240 ms and was managed by Thread: 5
Task for TitleId: 42 took 3322 ms and was managed by Thread: 5
Task for TitleId: 39 took 3393 ms and was managed by Thread: 5
Task for TitleId: 44 took 3502 ms and was managed by Thread: 8
Task for TitleId: 122 took 3583 ms and was managed by Thread: 5
Task for TitleId: 36 took 3697 ms and was managed by Thread: 8
Task for TitleId: 95 took 3744 ms and was managed by Thread: 5
Task for TitleId: 67 took 3871 ms and was managed by Thread: 8
Task for TitleId: 229 took 3896 ms and was managed by Thread: 5
Task for TitleId: 226 took 4034 ms and was managed by Thread: 8
Task for TitleId: 108 took 4078 ms and was managed by Thread: 5
Task for TitleId: 123 took 4213 ms and was managed by Thread: 8
Task for TitleId: 143 took 4285 ms and was managed by Thread: 5
Task for TitleId: 236 took 4364 ms and was managed by Thread: 8
Task for TitleId: 228 took 4466 ms and was managed by Thread: 5
Task for TitleId: 232 took 4540 ms and was managed by Thread: 6
Task for TitleId: 230 took 4641 ms and was managed by Thread: 5
Task for TitleId: 149 took 4715 ms and was managed by Thread: 6
Task for TitleId: 176 took 4793 ms and was managed by Thread: 5
Task for TitleId: 208 took 4902 ms and was managed by Thread: 6
Task for TitleId: 155 took 4946 ms and was managed by Thread: 5
Task for TitleId: 61 took 5057 ms and was managed by Thread: 6
Task for TitleId: 190 took 5097 ms and was managed by Thread: 5
Task for TitleId: 93 took 5262 ms and was managed by Thread: 5
Task for TitleId: 194 took 5280 ms and was managed by Thread: 5
Task for TitleId: 156 took 5419 ms and was managed by Thread: 6
Task for TitleId: 101 took 5440 ms and was managed by Thread: 5
Task for TitleId: 193 took 5572 ms and was managed by Thread: 6
Task for TitleId: 167 took 5598 ms and was managed by Thread: 5
Task for TitleId: 197 took 5730 ms and was managed by Thread: 6
Task for TitleId: 111 took 5755 ms and was managed by Thread: 5
Task for TitleId: 216 took 5882 ms and was managed by Thread: 6
Task for TitleId: 60 took 5930 ms and was managed by Thread: 5
Task for TitleId: 9 took 6059 ms and was managed by Thread: 5
Task for TitleId: 152 took 6085 ms and was managed by Thread: 5
Task for TitleId: 169 took 6218 ms and was managed by Thread: 6
Task for TitleId: 154 took 6264 ms and was managed by Thread: 5
Task for TitleId: 7 took 6403 ms and was managed by Thread: 6
Task for TitleId: 141 took 6506 ms and was managed by Thread: 5
Task for TitleId: 58 took 6560 ms and was managed by Thread: 6
Task for TitleId: 172 took 6670 ms and was managed by Thread: 5
Task for TitleId: 11 took 6730 ms and was managed by Thread: 6
Task for TitleId: 17 took 6846 ms and was managed by Thread: 5
Task for TitleId: 55 took 6912 ms and was managed by Thread: 6
Task for TitleId: 166 took 7020 ms and was managed by Thread: 5
Task for TitleId: 140 took 7069 ms and was managed by Thread: 6
Task for TitleId: 110 took 7177 ms and was managed by Thread: 5
Task for TitleId: 90 took 7222 ms and was managed by Thread: 6
Task for TitleId: 160 took 7352 ms and was managed by Thread: 5
Task for TitleId: 97 took 7400 ms and was managed by Thread: 6
Task for TitleId: 200 took 7503 ms and was managed by Thread: 5
Task for TitleId: 153 took 7556 ms and was managed by Thread: 6
Task for TitleId: 207 took 7654 ms and was managed by Thread: 5
Task for TitleId: 161 took 7721 ms and was managed by Thread: 6
Task for TitleId: 231 took 7810 ms and was managed by Thread: 5
Task for TitleId: 202 took 7873 ms and was managed by Thread: 6
Task for TitleId: 220 took 8068 ms and was managed by Thread: 6
One obvious misconception is that "and was managed by Thread:". Unless ExecuteTaskAsync is very badly implemented, there is no thread.
If the requests are being made to the same host, you might be running into service point manager limitations.
I have the following Scenario.
I take 50 jobs from the database into a blocking collection.
Each job is a long running one. (potentially could be). So I want to run them in a separate thread. (I know - it may be better to run them as Task.WhenAll and let the TPL figure it out - but I want to control how many runs simultaneously)
Say I want to run 5 of them simultaneously (configurable)
I create 5 tasks (TPL), one for each job and run them in parallel.
What I want to do is to pick up the next Job in the blocking collection as soon as one of the jobs from step 4 is complete and keep going until all 50 are done.
I am thinking of creating a Static blockingCollection and a TaskCompletionSource which will be invoked when a job is complete and then it can call the consumer again to pick one job at a time from the queue. I would also like to call async/await on each job - but that's on top of this - not sure if that has an impact on the approach.
Is this the right way to accomplish what I'm trying to do?
Similar to this link, but catch is that I want to process the next Job as soon as one of the first N items are done. Not after all N are done.
Update :
Ok, I have this code snippet doing exactly what I want, if someone wants to use it later. As you can see below, 5 threads are created and each thread starts the next job when it is done with current. Only 5 threads are active at any given time. I understand this may not work 100% like this always, and will have performance issues of context switching if used with one cpu/core.
var block = new ActionBlock<Job>(
job => Handler.HandleJob(job),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5 });
foreach (Job j in GetJobs())
block.SendAsync(j);
Job 2 started on thread :13. wait time:3600000ms. Time:8/29/2014
3:14:43 PM
Job 4 started on thread :14. wait time:15000ms. Time:8/29/2014
3:14:43 PM
Job 0 started on thread :7. wait time:600000ms. Time:8/29/2014
3:14:43 PM
Job 1 started on thread :12. wait time:900000ms. Time:8/29/2014
3:14:43 PM
Job 3 started on thread :11. wait time:120000ms. Time:8/29/2014
3:14:43 PM
job 4 finished on thread :14. 8/29/2014 3:14:58 PM
Job 5 started on thread :14. wait time:1800000ms. Time:8/29/2014
3:14:58 PM
job 3 finished on thread :11. 8/29/2014 3:16:43 PM
Job 6 started on thread :11. wait time:1200000ms. Time:8/29/2014
3:16:43 PM
job 0 finished on thread :7. 8/29/2014 3:24:43 PM
Job 7 started on thread :7. wait time:30000ms. Time:8/29/2014 3:24:43
PM
job 7 finished on thread :7. 8/29/2014 3:25:13 PM
Job 8 started on thread :7. wait time:100000ms. Time:8/29/2014
3:25:13 PM
job 8 finished on thread :7. 8/29/2014 3:26:53 PM
Job 9 started on thread :7. wait time:900000ms. Time:8/29/2014
3:26:53 PM
job 1 finished on thread :12. 8/29/2014 3:29:43 PM
Job 10 started on thread :12. wait time:300000ms. Time:8/29/2014
3:29:43 PM
job 10 finished on thread :12. 8/29/2014 3:34:43 PM
Job 11 started on thread :12. wait time:600000ms. Time:8/29/2014
3:34:43 PM
job 6 finished on thread :11. 8/29/2014 3:36:43 PM
Job 12 started on thread :11. wait time:300000ms. Time:8/29/2014
3:36:43 PM
job 12 finished on thread :11. 8/29/2014 3:41:43 PM
Job 13 started on thread :11. wait time:100000ms. Time:8/29/2014
3:41:43 PM
job 9 finished on thread :7. 8/29/2014 3:41:53 PM
Job 14 started on thread :7. wait time:300000ms. Time:8/29/2014
3:41:53 PM
job 13 finished on thread :11. 8/29/2014 3:43:23 PM
job 11 finished on thread :12. 8/29/2014 3:44:43 PM
job 5 finished on thread :14. 8/29/2014 3:44:58 PM
job 14 finished on thread :7. 8/29/2014 3:46:53 PM
job 2 finished on thread :13. 8/29/2014 4:14:43 PM
You can easily achieve what you need using TPL Dataflow.
What you can do is use BufferBlock<T>, which is a buffer for storing you data, and link it together with an ActionBlock<T> which will consume those requests as they're coming in from the BufferBlock<T>.
Now, the beauty here is that you can specify how many requests you want the ActionBlock<T> to handle concurrently using the ExecutionDataflowBlockOptions class.
Here's a simplified console version, which processes a bunch of numbers as they're coming in, prints their name and Thread.ManagedThreadID:
private static void Main(string[] args)
{
var bufferBlock = new BufferBlock<int>();
var actionBlock =
new ActionBlock<int>(i => Console.WriteLine("Reading number {0} in thread {1}",
i, Thread.CurrentThread.ManagedThreadId),
new ExecutionDataflowBlockOptions
{MaxDegreeOfParallelism = 5});
bufferBlock.LinkTo(actionBlock);
Produce(bufferBlock);
Console.ReadKey();
}
private static void Produce(BufferBlock<int> bufferBlock)
{
foreach (var num in Enumerable.Range(0, 500))
{
bufferBlock.Post(num);
}
}
You can also post them asynchronously if needed, using the awaitable BufferBlock.SendAsync
That way, you let the TPL handle all the throttling for you without needing to do it manually.
You can use BlockingCollection and it will work just fine, but it was built before async-await so it blocks synchronously which could be less scalable in most cases.
You're better off using async ready TPL Dataflow as Yuval Itzchakov suggested. All you need is an ActionBlock that processes each item concurrently with a MaxDegreeOfParallelism of 5 and you post your work to it synchronously (block.Post(item)) or asynchronously (await block.SendAsync(item)):
private static void Main()
{
var block = new ActionBlock<Job>(
async job => await job.ProcessAsync(),
new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 5});
for (var i = 0; i < 50; i++)
{
block.Post(new Job());
}
Console.ReadKey();
}
You could do this with a SemaphoreSlim like in this answer, or using ForEachAsync like in this answer.