Skip to content

Repository files navigation

Welcome to the world of Parallel Programming in .NET

This repository's main purpose is to demonstrate the various tools that the .NET ecosystem provides us to write code that can run in parallel. Feel free to contribute. :)

Table of Contents

  1. Required environment
  2. Demo Application
    2.1) Static File server
    2.2) Throttled Downloader library
    2.3) Benchmark tool
  3. Instructions for running
    3.1) Benchmark
    3.2) Debug
  4. Demonstrated tools
    4.1) Baselines
    4.2) Low level abstractions
    4.3) Mid level abstractions
    4.4) High level abstractions
  5. Sample benchmark result
  6. .NET Profiling
  7. Known missing sample codes

Required environment

  • .NET Core 3.0
  • Visual Studio 2019

Demo Application

In order to demonstrate the different capabilities, we need a demo application.
In our case this app will be a throttled parallel downloader. The solution contains three projects:

I. - Static File server

It is an ASP.NET Core 3.0 web-application which can serve static files for http clients.
It exposes the files under the Resources folder through the /resources route.
Related project: ThrottledParallelism

II. - Throttled Downloader library

It is a .NET Core 3.0 library, which is exposing a simple API and several implementations of it.

publicinterfaceIGovernedParallelDownloader{TaskDownloadThemAllAsync(IEnumerable<Uri>uris,ProcessResultprocessResult,bytemaxThreads);}

Related project: LogFileServer

III. - Benchmark tool

It is a .NET Core 3.0 console application, which is used to perform micro-benchmarking. It measures execution time, GC cycles, etc.
Related project: RunEverythingInParallel


NOTE

Please note that this demo is I/O bound.
Which means that using techniques like Task.Run or Parallel.XYZ, which are CPU-bound, does not make too much sense, because they are limited to the number of cores in the machine.
So, please scrutinize the provided examples with this in mind.


Instructions for running

Benchmark

  1. Make sure that Program.cs of the RunEverythingInParallel project look like this:
usingSystem;usingSystem.Threading;usingBenchmarkDotNet.Running;//BenchmarkRunnernamespaceRunEverythingInParallel{classProgram{//Use Release staticvoidMain(string[]args){Thread.Sleep(1000);//Wait for the WebApp to startBenchmarkRunner.Run<ThrottledDownloader>();//Add as many benchmarks as you want to runConsole.ReadLine();}}}
  1. Build the solution in Release mode (Set the Optimize node in the csproj to true if it is needed)
  2. Hit Ctrl+F5 in Visual Studio
  3. If you want to run it without VS (by using the dotnet cli) then run the LogFileServer project prior the RunEverythingInParallel

Debug

  1. Make sure that Program.cs of the RunEverythingInParallel project look like this:
usingSystem;usingSystem.Threading;usingThrottledParallelism.Strategies;namespaceRunEverythingInParallel{classProgram{//Use DebugstaticvoidMain(string[]args){Thread.Sleep(1000);//Wait for the WebApp to startvardownloader=newThrottledDownloader();downloader.Setup();downloader.RunExperiment<HighLevel_Foreach_AsParallel>();}}}
  1. Build the solution in Debug mode (Set the Optimize node in the csproj to false if it is needed)
  2. Hit F5 in Visual Studio
  3. Analyze the choosen code by using the Parallel Watch, Parallel Stack and Tasks windows

Demonstrated tools

Baselines

No.ChannelSynchronizerWorkers viaThrottled byFile
1IEnumerable-main thread-Link
2IEnumerableTask.WhenAllTask-Link

Low level abstractions

No.ChannelSynchronizerWorkers viaThrottled byFile
1BlockingCollectionAsyncCountdownEventThreadPool.QueueUserWorkItemManually (for (i = 0; i < maxThreads; i++))Link
2BlockingCollectionCountdownEventThreadPool.QueueUserWorkItemManually (for (i = 0; i < maxThreads; i++))Link
3BlockingCollectionParent TaskChildren TasksManually (for (i = 0; i < maxThreads; i++))Link
4BlockingCollectionTask.WhenAllTaskManually (for (i = 0; i < maxThreads; i++))Link
5IEnumerable<KeyValuePair<Uri, Func<Uri, Task>>Task.WhenAllTaskLoad balancing by MoreLinq's SegmentLink
6IGrouping<int, Job>Task.WhenAllTaskLoad balancing by MoreLinq's GroupAdjacentLink

Mid level abstractions

No.ChannelSynchronizerWorkers viaThrottled byFile
1ActionBlockCancellationTokenSource + InterlockedTaskExecutionDataflowBlockOptionsLink
2ActionBlock + BatchBlockPropagateCompletion + CompletionTaskExecutionDataflowBlockOptionsLink
3BufferBlockTask.WhenAll + ImmutableListTaskManually (for (i = 0; i < maxThreads; i++))Link
4ChannelTask.WhenAllTaskManually (Enumerable.Range(0, maxThreads -1))Link

High level abstractions

No.ChannelSynchronizerWorkers viaThrottled byFile
1PartitionerParallel.ForeachTask + AsyncHelper.RunSync!!!ParallelOptionsLink
2IGrouping<int, Uri>Parallel.InvokeTask + AsyncHelper.RunSync!!!GroupBy (round robin)Link
3IGrouping<int, Uri>Parallel.For +TLSTask + AsyncHelper.RunSync!!!GroupBy + Parallel.ForLink
4ConcurrentQueueTask.WhenAllTaskManually (Enumerable.Range(0, maxThreads -1))Link
5IEnumerableParallelForEachAsyncTaskParalellelForEachAsyncLink
6HashSetTask.WhenAnyTaskManually only during initializationLink
7IAsyncEnumerableawait last TaskTaskSemaphoreSlimLink
8ParallelQueryTask.WhenAllTaskWithDegreeOfParallelismLink
9ParallelQueryCustom AwaiterTaskWithDegreeOfParallelismLink
10IEnumerableTask.WhenAllTaskSemaphoreSlimLink
11IEnumerableTask.WhenAllTaskBulkheadAsyncLink

Sample benchmark result

BenchmarkDotNet=v0.11.5
OS=Windows 10.0.17134.1069 (1803/April2018Update/Redstone4)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores Frequency=2062501 Hz, Resolution=484.8482 ns .NET Core SDK=3.0.100
Host : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
IterationCount=3 RunStrategy=ColdStart

MethodMeanErrorStdDevRatioRatioSDGen 0Gen 1Gen 2Allocated
BaseLine_Sequentially3.151 s1.3267 s0.0727 s1.000.00377000.000047000.000038000.00001600419.34 KB
BaseLine_EmbarrassinglyParallel1.055 s1.8874 s0.1035 s0.330.03270000.00001000.00001000.000058.29 KB
CSharp31.426 s0.7124 s0.0390 s0.450.02320000.00008000.00007000.00001.98 KB
CSharp51.943 s1.6989 s0.0931 s0.620.02383000.00004000.00004000.00004.32 KB
CSharp61.318 s0.6582 s0.0361 s0.420.00367000.00001000.00001000.000022.23 KB
CSharp81.340 s1.8518 s0.1015 s0.420.02300000.00004000.00004000.000013.41 KB
Bonus1.999 s2.2747 s0.1247 s0.630.04405000.00003000.00003000.000030.4 KB

.NET Profiling

If you want to deep dive into the execution details, I highly recommend you to use some profiling.
If sampling is enough for you, then I encourage you to use CodeTrack

If tracing is needed, then you can play with the Concurrency Visualizerstep-by-step

Known missing sample codes

  • Reactive eXtensions
  • ideas are more than welcome

About

It is dedicated to demonstrate the parallel programming capabilities in dotnet

Topics

Resources

Stars

11 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages