PageRenderTime 67ms CodeModel.GetById 2ms app.highlight 56ms RepoModel.GetById 1ms app.codeStats 0ms

/code-samples/threading/README.md

https://bitbucket.org/BanksySan/banksysan.workshops.advancedcsharp
Markdown | 1255 lines | 1016 code | 239 blank | 0 comment | 0 complexity | 114fb45557367d300ed37508e1768528 MD5 | raw file
   1# Threading
   2
   3## Defining a thread
   4
   5This isn't an easy thing to do, as I discovered when I did a presentation on async JavaScript.  The term _thread_ is used very liberally for similar things.  It seems best to define it on a per-use basis.
   6
   71. A process will consist of one or more threads.
   81. A thread is the basic unit through which the operating system can assign processor time.
   91. A processor can execute one thread at any given moment.
  101. A thread runs to completion and is then disposed.
  11
  12If we had a single processor and no concept of threads then each process would block all the other operations until it had finished.
  13
  14Threads are an answer to this problem.  The OS will share time on the processor between the treads requesting it.  This means that even if a thread does have an infinite loop the OS will still swap it out so the other threads keep working.
  15
  16Nothing is ever free though, the management system required to perform threading used resources itself and the algorithm used to decide what thread should get precious processor time isn't perfect.  This is the reason that .NET has friendlier classes available to abstract away some of the ugliness.
  17
  18## Creating a Thread
  19
  20> If you create threads directly, then you're code is already obsolete.
  21
  22Whilst this is true for almost all new development, it's still useful to understand them is only to understand what the problems with them are.
  23
  24> The managed threads created in C# map one-to-one with Windows threads.  Originally there was an idea that a managed thread would be a thing, it didn't happen though.  This is why you have a thread ID and a managed thread ID.
  25
  26Creating and running a thread is trivial.  All we need to do is create a method that's assignable to a `ThreadStart` delegate and pass it to the constructor of the `Thread` type.
  27
  28``` csharp
  29using static System.Console;
  30using System.Threading;
  31
  32static class Program
  33{
  34    private static void Main()
  35    {
  36        WriteLine($"Main managed thread ID: {Thread.CurrentThread.ManagedThreadId}.");
  37        var thread = new Thread(Counter);
  38        WriteLine($"Created thread.  Managed thread ID: {thread.ManagedThreadId}.");
  39        thread.Start();
  40        WriteLine($"Thread started.");
  41    }
  42
  43    private static void Counter()
  44    {
  45        for (var i = 0; i < 10; i++)
  46        {
  47            WriteLine($"{i}:  Managed thread ID: {Thread.CurrentThread.ManagedThreadId}.");
  48            Thread.Sleep(100);
  49        }
  50    }
  51}
  52```
  53
  54Compile and run this code.
  55
  56    Main managed thread ID: 1.
  57    Created thread.  Managed thread ID: 3.
  58    Thread started.
  59    0:  Managed thread ID: 3.
  60    1:  Managed thread ID: 3.
  61    2:  Managed thread ID: 3.
  62    3:  Managed thread ID: 3.
  63    4:  Managed thread ID: 3.
  64    5:  Managed thread ID: 3.
  65    6:  Managed thread ID: 3.
  66    7:  Managed thread ID: 3.
  67    8:  Managed thread ID: 3.
  68    9:  Managed thread ID: 3.
  69
  70This is no different than what we'd see if you hadn't used threads at all, in order to see threads interplaying we need another one:
  71
  72``` csharp
  73using static System.Console;
  74using System.Threading;
  75
  76static class Program
  77{
  78    private static void Main()
  79    {
  80        WriteLine($"Main managed thread ID: {Thread.CurrentThread.ManagedThreadId}.");
  81
  82        var thread1 = new Thread(Counter);
  83        WriteLine($"Created thread 1.  Managed thread ID: {thread1.ManagedThreadId}.");
  84        thread1.Start();
  85        WriteLine($"Thread 1 started.");
  86
  87        var thread2 = new Thread(Counter);
  88        WriteLine($"Created thread 2.  Managed thread ID: {thread2.ManagedThreadId}.");
  89        thread2.Start();
  90        WriteLine($"Thread 2 started.");
  91    }
  92
  93    private static void Counter()
  94    {
  95        for (var i = 0; i < 10; i++)
  96        {
  97            WriteLine($"{i}:  Managed thread ID: {Thread.CurrentThread.ManagedThreadId}.")Thread.Sleep(100);
  98        }
  99    }
 100}
 101```
 102
 103Now we can see the threads operating in parallel:
 104
 105    Main managed thread ID: 1.
 106    Created thread 1.  Managed thread ID: 3.
 107    Thread 1 started.
 108    Created thread 2.  Managed thread ID: 4.
 109    0:  Managed thread ID: 3.
 110    Thread 2 started.
 111    0:  Managed thread ID: 4.
 112    1:  Managed thread ID: 4.
 113    1:  Managed thread ID: 3.
 114    2:  Managed thread ID: 3.
 115    2:  Managed thread ID: 4.
 116    3:  Managed thread ID: 4.
 117    3:  Managed thread ID: 3.
 118    4:  Managed thread ID: 4.
 119    4:  Managed thread ID: 3.
 120    5:  Managed thread ID: 4.
 121    5:  Managed thread ID: 3.
 122    6:  Managed thread ID: 4.
 123    6:  Managed thread ID: 3.
 124    7:  Managed thread ID: 4.
 125    7:  Managed thread ID: 3.
 126    8:  Managed thread ID: 4.
 127    8:  Managed thread ID: 3.
 128    9:  Managed thread ID: 4.
 129    9:  Managed thread ID: 3.
 130
 131In this example the threads happened to run in perfect parallel, however the OS scheduler is free to manage them however it sees fit.  It could even run one to completion and then the next if it deemed that more optimal for the wider system.
 132
 133## Foreground v Background Treads
 134
 135You probably noticed in the previous examples that the application didn't exit until all the threads had finished.  This might be unexpected behaviour to you, it was to me initially.  The reason we get this behavior is because the CLR has a concept of threads being either _background_ or _foreground_.  The threads are identical with one behavioral difference.  An application will terminate when all foreground threads have returned and will terminate any remaining background threads.
 136
 137``` csharp
 138using System.Threading;
 139using static System.Console;
 140
 141static class BackgroundAndForegroundThreads
 142{
 143    private static void Main()
 144    {
 145        var backgroundThread = new Thread(Counter)
 146        {
 147            IsBackground = true
 148        };
 149
 150        var foregroundThread = new Thread(Counter);
 151
 152        WriteLine($"Starting both threads.");
 153        backgroundThread.Start();
 154        foregroundThread.Start();
 155        Thread.Sleep(20);
 156        WriteLine("We'll kill the foreground thread and the application will exit...");
 157        foregroundThread.Abort();
 158    }
 159
 160    private static void Counter()
 161    {
 162        while (true)
 163        {
 164            WriteLine($"Thread ID: {Thread.CurrentThread.ManagedThreadId}, Is Background: {Thread.CurrentThread.IsBackground}.");
 165            Thread.Sleep(5);
 166        }
 167    }
 168}
 169```
 170
 171This will produce something like the following:
 172
 173    Starting both threads.
 174    Thread ID: 3, Is Background: True.
 175    Thread ID: 4, Is Background: False.
 176    Thread ID: 3, Is Background: True.
 177    Thread ID: 4, Is Background: False.
 178    Thread ID: 3, Is Background: True.
 179    Thread ID: 4, Is Background: False.
 180    Thread ID: 4, Is Background: False.
 181    Thread ID: 3, Is Background: True.
 182    We'll kill the foreground thread and the application will exit...
 183    Thread ID: 3, Is Background: True.
 184    Thread ID: 3, Is Background: True.
 185
 186Try killing the background thread instead, you'll see that the application never finishes because `Counter()` never returns.
 187
 188## Synchronising Threads
 189
 190Almost always, at some point, we need to synchronise threads in some way.  Normally synchronisation is so they can manipulate some shared data or prevent a race condition error.  We have a number of requirements for synchronisation which we'll go through next.
 191
 192### Waiting for a Thread to Complete
 193
 194The simplest synchronisation is just waiting for completion of some other thread.  This is done my joining the other thread back.
 195
 196``` csharp
 197using System.Threading;
 198using static System.Console;
 199
 200static class ThreadJoining
 201{
 202    private static void Main()
 203    {
 204        var thread = new Thread(Counter) { IsBackground = true };
 205        thread.Start();
 206        for(var i = 0; i < 5; i++)
 207        {
 208            WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Working on iteration {i}.");
 209            Thread.Sleep(20);
 210        }
 211
 212        WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Finished! waiting...");
 213        thread.Join();
 214        WriteLine($"{Thread.CurrentThread.ManagedThreadId}: All done.");
 215    }
 216
 217    private static void Counter()
 218    {
 219        for (var i = 0; i < 10; i++)
 220        {
 221            WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Working on iteration {i}.");
 222            Thread.Sleep(20);
 223        }
 224
 225        WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Finished!");
 226    }
 227}
 228```
 229
 230When you run this you'll see that the execution of the `Main()` pauses at the `Join()` method.
 231
 232    1: Working on iteration 0.
 233    3: Working on iteration 0.
 234    1: Working on iteration 1.
 235    3: Working on iteration 1.
 236    1: Working on iteration 2.
 237    3: Working on iteration 2.
 238    1: Working on iteration 3.
 239    3: Working on iteration 3.
 240    1: Working on iteration 4.
 241    3: Working on iteration 4.
 242    1: Finished! waiting...
 243    3: Working on iteration 5.
 244    3: Working on iteration 6.
 245    3: Working on iteration 7.
 246    3: Working on iteration 8.
 247    3: Working on iteration 9.
 248    3: Finished!
 249    1: All done.
 250
 251## Exceptions
 252
 253It's important to note that though background threads don't contribute to an application's lifetime when things are going well; they will still cause the application to terminate when an unhandled exception occurs.
 254
 255``` csharp
 256using System;
 257using System.Threading;
 258using static System.Console;
 259
 260internal static class ExceptionHandling
 261{
 262    private static void Main()
 263    {
 264
 265        var thread = new Thread(ThrowsDummyException);
 266        thread.Start();
 267        while (true)
 268        {
 269            WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} is working hard...");
 270            Thread.Sleep(10);
 271        }
 272    }
 273
 274    private static void ThrowsDummyException()
 275    {
 276        var timeSpan = TimeSpan.FromMilliseconds(100);
 277
 278        WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}: Waiting {timeSpan.TotalSeconds} seconds to throw.");
 279        Thread.Sleep(timeSpan);
 280        throw new Exception("BOOM!");
 281    }
 282}
 283```
 284
 285You might be tempted to solve this problem by adding a `try-catch` in the `Main` method like this:
 286
 287``` csharp
 288using System;
 289using System.Threading;
 290using static System.Console;
 291
 292internal static class ExceptionHandling
 293{
 294    private static void Main()
 295    {
 296        try
 297        {
 298            var thread = new Thread(ThrowsDummyException);
 299            thread.Start();
 300            while (true)
 301            {
 302                WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} is working hard...");
 303                Thread.Sleep(10);
 304            }
 305        }
 306        catch (System.Exception)
 307        {
 308            System.Console.WriteLine($"I caught the exception.");
 309        }
 310
 311    }
 312
 313    private static void ThrowsDummyException()
 314    {
 315        var timeSpan = TimeSpan.FromMilliseconds(100);
 316
 317        WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}: Waiting {timeSpan.TotalSeconds} seconds to throw.");
 318        Thread.Sleep(timeSpan);
 319        throw new Exception("BOOM!");
 320    }
 321}
 322```
 323
 324This doesn't actually catch the exception though, it can't because `thread` is running asynchronously to the `Main` method.  Exception handling needs to be done in the thread that's throwing the exception.  In the example above, a `try-catch` would be used in the `ThrowsDummyException` method.
 325
 326## Aborting a Thread
 327
 328Once a thread is started another thread can't just stop it. A thread is unloaded when it either finishes, the app domain it's in is unloaded or an exception is thrown in the thread.
 329
 330This third option is common and the CLR has a special exception class for doing this.  Calling `Abort` on a thread tells the CLR to raise a `ThreadAbortedException` in that thread.
 331
 3321. The `ThreadAbortedException` is sealed.
 3331. The `ThreadAbortedException` has no public constructor.
 3341. The CLR will not terminate an application if a `ThreadAbortedException` is raised.
 3351. You can catch a `ThreadAbortedException` **however**, after handling it the `ThreadAbortedException` will continue to bubble up **unless** `ResetAbort` is called.
 336
 337The last point here means that whilst you can try to abort a thread, you can't guarantee when or if is will actually happen.
 338
 339``` csharp
 340using static System.Console;
 341using System.Threading;
 342
 343static class AbortingAThread
 344{
 345    private static void Main()
 346    {
 347        var thread = new Thread(Counter);
 348        thread.Start();
 349        Thread.Sleep(50);
 350        thread.Abort("I'm aborting you.");
 351    }
 352    private static void Counter()
 353    {
 354        try
 355        {
 356            var i = 0;
 357            while(true)
 358            {
 359                WriteLine($"{Thread.CurrentThread.ManagedThreadId}: {i}");
 360                Thread.Sleep(10);
 361            }
 362        }
 363        catch(ThreadAbortException e)
 364        {
 365            WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}:  I caught the `ThreadAbortedException` exception: {e.Message};");
 366            WriteLine($"The object data is: {e.ExceptionState}");
 367        }
 368    }
 369}
 370```
 371
 372Whilst we can't catch the exception to stop it from being re-thrown, we can cancel it completely by calling `ResetAbort()`.
 373
 374``` csharp
 375using static System.Console;
 376using System.Threading;
 377
 378static class ResettingAnAbortingThread
 379{
 380    private static void Main()
 381    {
 382        var thread = new Thread(Counter);
 383        thread.Start();
 384        Thread.Sleep(50);
 385        thread.Abort("I'm aborting you.");
 386    }
 387    private static void Counter()
 388    {
 389        try
 390        {
 391            var i = 0;
 392            while(true)
 393            {
 394                WriteLine($"{Thread.CurrentThread.ManagedThreadId}: {i}");
 395                Thread.Sleep(10);
 396            }
 397        }
 398        catch(ThreadAbortException e)
 399        {
 400            WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}:  I caught the `ThreadAbortedException` exception: {e.Message};");
 401            WriteLine($"The object data is: {e.ExceptionState}");
 402            Thread.ResetAbort();
 403        }
 404
 405        WriteLine("Wee!  I wasn't aborted!");
 406    }
 407}
 408```
 409
 410## Parameterized Threads
 411
 412The `Thread` constructor is overloaded to accept either a `ThreadStart` delegate or a `ParameterizedThreadStart`.  The `ParameterizedThreadStart` delegate accepts a single `object` as an argument.
 413
 414``` csharp
 415using System.Threading;
 416using static System.Console;
 417
 418static class ParameterizedThreads
 419{
 420    private static void Main()
 421    {
 422        var thread = new Thread(CountInterval);
 423        thread.Start("Hello World!");
 424        thread.Join();
 425    }
 426
 427    private static void CountInterval(object message)
 428    {
 429        WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: Message: {(string) message}.");
 430    }
 431}
 432```
 433
 434## Thread Priority Levels
 435
 436Windows uses a threads priority (relative to other threads competing for processor time) to decide what thread gets processor time.  It will share the processor time between the highest priority threads that are ready at the exclusion of all lower priority threads.  This means that you must exercise care in setting a thread's priority - in fact, there are some priorities that can only be set by a user with kernel-mode permissions.
 437
 438> The absolute priority is a number from 0 to 31 (inclusive).  0 is the lowest priority and 31 is the highest.  0 however is reserved for the sole use of the _zero page thread_ that is created by the OS.
 439
 440There are two values that affect the calculation of the absolute priority of a thread:
 441
 4421. The thread's `ThreadPriority` value.
 4431. The process's priority class.
 444
 445They are calculated as:
 446
 447![Thread priorities](https://i.imgur.com/15Z4Pr9.png)
 448
 449What this means is that you could create a high priority, long running thread which would block other threads from executing.  This is called _thread starvation_.
 450
 451We can see this happening with the following code:
 452
 453``` csharp
 454using System;
 455using System.Collections.Generic;
 456using System.Linq;
 457using System.Threading;
 458using static System.Console;
 459
 460static class ThreadStarvation
 461{
 462    private static void Main()
 463    {
 464        var processorCount = Environment.ProcessorCount;
 465
 466        WriteLine($"There are {processorCount} processors.");
 467        WriteLine($"There can be {processorCount} threads running at the same time.");
 468
 469        var aboveNormalPriorityThreads = new HashSet<Thread>();
 470
 471        for (var i = 0; i < processorCount; i ++)
 472        {
 473            aboveNormalPriorityThreads.Add(new Thread(Counter) { Priority = ThreadPriority.AboveNormal });
 474        }
 475
 476        var normalPriorityThread = new Thread(Counter);
 477        normalPriorityThread.Start();
 478        WriteLine($"Letting the normal priority thread run for a bit");
 479        Thread.Sleep(1000);
 480        foreach(var thread in aboveNormalPriorityThreads)
 481        {
 482            WriteLine($"Starting {thread.ManagedThreadId} with priority {thread.Priority}.");
 483            thread.Start();
 484        }
 485    }
 486
 487    private static void Counter()
 488    {
 489        var currentThread = Thread.CurrentThread;
 490        var priority = currentThread.Priority;
 491        var id = currentThread.ManagedThreadId;
 492
 493        for (var i = 0; i < 5; i++)
 494        {
 495            var then = DateTime.Now;
 496            var number = then.GetHashCode();
 497            while (DateTime.Now < then.AddSeconds(1))
 498            {
 499                number ^= number.GetHashCode();
 500            }
 501
 502            WriteLine($"Thread ID: {id}, Priority: {priority, -15}{i, 10}{number}");
 503        }
 504
 505        WriteLine($"Thread ID: {id}, Priority: {priority, -15}COMPLETED.");
 506    }
 507}
 508```
 509
 510Here we create a number of high priority threads, enough to saturate the processors we have.  We also create low priority thread.  Even though the low priority thread is started before the higher priority ones it's starved of resources until one of the higher priority threads complete.
 511
 512    There are 4 processors.
 513    There can be 4 threads running at the same time.
 514    Letting the normal priority thread run for a bit
 515    Starting 3 with priority AboveNormal.
 516    Thread ID: 7, Priority: Normal                  00
 517    Starting 4 with priority AboveNormal.
 518    Starting 5 with priority AboveNormal.
 519    Starting 6 with priority AboveNormal.
 520    Thread ID: 4, Priority: AboveNormal             00
 521    Thread ID: 3, Priority: AboveNormal             00
 522    Thread ID: 5, Priority: AboveNormal             00
 523    Thread ID: 6, Priority: AboveNormal             00
 524    Thread ID: 4, Priority: AboveNormal             10
 525    Thread ID: 5, Priority: AboveNormal             10
 526    Thread ID: 3, Priority: AboveNormal             10
 527    Thread ID: 6, Priority: AboveNormal             10
 528    Thread ID: 3, Priority: AboveNormal             20
 529    Thread ID: 4, Priority: AboveNormal             20
 530    Thread ID: 5, Priority: AboveNormal             20
 531    Thread ID: 6, Priority: AboveNormal             20
 532    Thread ID: 3, Priority: AboveNormal             30
 533    Thread ID: 5, Priority: AboveNormal             30
 534    Thread ID: 4, Priority: AboveNormal             30
 535    Thread ID: 6, Priority: AboveNormal             30
 536    Thread ID: 3, Priority: AboveNormal             40
 537    Thread ID: 3, Priority: AboveNormal    COMPLETED.
 538    Thread ID: 5, Priority: AboveNormal             40
 539    Thread ID: 5, Priority: AboveNormal    COMPLETED.
 540    Thread ID: 7, Priority: Normal                  10
 541    Thread ID: 4, Priority: AboveNormal             40
 542    Thread ID: 4, Priority: AboveNormal    COMPLETED.
 543    Thread ID: 6, Priority: AboveNormal             40
 544    Thread ID: 6, Priority: AboveNormal    COMPLETED.
 545    Thread ID: 7, Priority: Normal                  20
 546    Thread ID: 7, Priority: Normal                  30
 547    Thread ID: 7, Priority: Normal                  40
 548    Thread ID: 7, Priority: Normal         COMPLETED.
 549
 550Notice as well that the lower priority thread is actually stopped mid-execution to make room for the higher priority threads.  When you run this you probably notice that the performance of the system degrades for the ten-ish seconds it's running for.
 551
 552## The Cost of Threads
 553
 554Aside from the problems demonstrated above and the need for you as the developer to optimise for an unknown number of CPUs you will also have to conciser the other substantial overheads associated with creating and managing threads.
 555
 556### The Upfront Cost
 557
 558Each thread created has is initialized with some data structures.  A kernel object, a thread environment block, user stack and kernel stack and DLL thread-attach and thread detach notifications.
 559
 560The kernel object itself it used by the OS kernel to reference the thread.
 561
 562The stacks contain the processing information for user and kernel mode.  Kernel mode and user mode can't pass by reference, so any state that needs to be passed between them needs to be copied.
 563
 564The DLL thread attach and detach list the `DllMain` method for every unmanaged DLL loaded.  With some exceptions, it will call this method when it loads and unloads with different flags (i.e. `DLL_THREAD_ATTACH` and `DLL_THREAD_DETACH`);
 565
 566### The Ongoing Costs
 567
 568The hardware processor contains many optimisations, one of which is the cache.  When a thread is processing the cache hugely speeds up data access for recently accessed data items.  Swapping unrelated threads in and out of the processor makes the cache useless and slower then if it weren't there at all.
 569
 570In order to solve this problem, each thread stores it's cache in the kernel object and has to reload the cache in the processor each time it is granted processor time.
 571
 572## Possible Solutions
 573
 574I use the term _solution_ rather flippantly here.  There are many best practices that help us avoid the problems described above to some extent or another, but the have drawbacks as well.  That being acknowledged, for most scenarios these are better options than the manual thread manipulation we've looked at so far.
 575
 576### Thread Pools
 577
 578We can avoid the overhead of thread creation by reusing threads that have already been created.  The `ThreadPool` class handles this for us.
 579
 580``` csharp
 581using System.Collections.Generic;
 582using System.Collections.Concurrent;
 583using System.Linq;
 584using System.Threading;
 585using static System.Console;
 586
 587static class ThreadPoolClass
 588{
 589    private const int THREAD_COUNT = 100;
 590    private static readonly bool[] COMPLETION = new bool[THREAD_COUNT];
 591    private static readonly ConcurrentStack<int> USAGE = new ConcurrentStack<int>();
 592
 593    private static void Main()
 594    {
 595        for (var i = 0; i < THREAD_COUNT; i++)
 596        {
 597            WriteLine($"Queueing {i}.");
 598            ThreadPool.QueueUserWorkItem(Counter, i);
 599        }
 600
 601        while (!COMPLETION.All(x => x))
 602        {
 603            Thread.Sleep(0);
 604        }
 605
 606        var threadUsages = new SortedDictionary<int, int>();
 607
 608        foreach (var usage in USAGE)
 609        {
 610            if (threadUsages.ContainsKey(usage))
 611            {
 612                threadUsages[usage] = threadUsages[usage] + 1;
 613            }
 614            else
 615            {
 616                threadUsages.Add(usage, 1);
 617            }
 618        }
 619
 620        WriteLine($"Thread reuse:");
 621        foreach (var usage in threadUsages)
 622        {
 623            WriteLine($"Thread {usage.Key} used {usage.Value} times.");
 624        }
 625    }
 626
 627    private static void Counter(object state)
 628    {
 629        var index = (int)state;
 630        USAGE.Push(Thread.CurrentThread.ManagedThreadId);
 631
 632        for (var i = 0; i < 10; i++)
 633        {
 634            WriteLine($"Thread ID: {Thread.CurrentThread.ManagedThreadId}: {i}");
 635            Thread.Sleep(1);
 636        }
 637        COMPLETION[index] = true;
 638    }
 639}
 640```
 641
 642When you run this, you'll see something like the following at the end of the output:
 643
 644    Thread reuse:
 645    Thread 3 used 22 times.
 646    Thread 4 used 22 times.
 647    Thread 5 used 23 times.
 648    Thread 6 used 22 times.
 649    Thread 7 used 11 times.
 650
 651This might seem like the silver bullet, however we lose a lot of benefits from the manual creation.
 652
 6531. No way to chose a thread's priority.
 6541. No way to wait for a thread to finish.
 6551. The thread is in an unknown state.  This means that there could be unexpected values in the TLS, maybe even secret stuff from whatever it was doing last.
 6561. Thread pool threads are always background threads.
 657
 658## The Task Type
 659
 660The `Task` and `Task<T>` types address the problem of waiting until completion.  If we change the `ThreadPool` example above to use tasks then we get exactly the same output, but we don't need the `while` loop and boolean array to wait for tasks to all finish.  We can just use the `Task.WaitAll` method.
 661
 662Internally the task is using the `ThreadPool`, which is why our output is identical.
 663
 664## Cancelling Threads
 665
 666Cancellation has two _modes_ that can be used for cancelling.
 667
 6681. Marking to the subject thread that it's creator wants it to cancel.
 6691. The subject thread throwing a `OperationCancelledException` if cancellation has been requested.
 670
 671Cancelling threads is a cooperative pattern.  We tell the thread that we want to cancel it, it is up to the executing code to then decide what action to take.  The thread is under no obligation to cancel at all.  A _well written_ thread will cancel if it is safe to do so.  This isn't dissimilar to the disposal of objects, in that the object will perform any cleaning up actions it deems necessary before being collected.
 672
 673Both these methods are shown here:
 674
 675``` csharp
 676using System;
 677using System.Threading;
 678using static System.Threading.Thread;
 679using static System.Console;
 680
 681internal static class ThreadCancellation
 682{
 683    private static void Main()
 684    {
 685        var cancellationTokenSource = new CancellationTokenSource();
 686        var cancellationToken = cancellationTokenSource.Token;
 687        ThreadPool.QueueUserWorkItem(x => Counter(cancellationToken));
 688        ThreadPool.QueueUserWorkItem(x => CounterWithThrow(cancellationToken));
 689        Sleep(10);
 690        cancellationTokenSource.Cancel();
 691        Sleep(10);
 692    }
 693
 694    private static void Counter(CancellationToken cancellationToken)
 695    {
 696        WriteLine($"Counter running on {CurrentThread.ManagedThreadId}.");
 697        while (!cancellationToken.IsCancellationRequested)
 698        {
 699            WriteLine($"Thread {CurrentThread.ManagedThreadId}:  {DateTime.Now.Ticks}");
 700            Sleep(1);
 701        }
 702
 703        WriteLine("Counter() was canceled");
 704    }
 705
 706    private static void CounterWithThrow(CancellationToken cancellationToken)
 707    {
 708        WriteLine($"CounterWithThrow running on {CurrentThread.ManagedThreadId}.");
 709
 710        try
 711        {
 712            while (true)
 713            {
 714                WriteLine($"Thread {CurrentThread.ManagedThreadId}:  {DateTime.Now.Ticks}");
 715                cancellationToken.ThrowIfCancellationRequested();
 716                Sleep(1);
 717            }
 718
 719        }
 720        catch (OperationCanceledException e)
 721            {
 722            WriteLine($"Thread {CurrentThread.ManagedThreadId}: Caught OperationCanceledException: {e.Message}");
 723        }
 724    }
 725}
 726```
 727
 728Both `Counter` and `CancellationToken`  accept a `CancellationToken` token (created by the same `CancellationTokenSource`) and both keep processing until `Cancel()` is called on the token's source; however `Counter()` just exits gracefully by querying the `IsCancellationRequest` property whereas `CounterWithThrow()` catches the exception that's thrown.  If we didn't catch this exception then the whole application would fail.
 729
 730We can also catch the exception in the parent thread.  If an exception is thrown then `Cancel()`.
 731
 732## Registering a Callback
 733
 734We can also register callbacks to be invoked when a cancellation is called:
 735
 736``` csharp
 737using System;
 738    using System.Threading;
 739    using static System.Console;
 740    using static System.Threading.Thread;
 741
 742    internal class RegisterCancellationCallback
 743    {
 744        private static void Main()
 745        {
 746            var cancellationTokenSource = new CancellationTokenSource();
 747            var cancellationToken = cancellationTokenSource.Token;
 748            ThreadPool.QueueUserWorkItem(x => Counter(cancellationToken));
 749            ThreadPool.QueueUserWorkItem(x => CounterWithThrow(cancellationToken));
 750            cancellationToken.Register(LogCanceled);
 751            Sleep(10);
 752            cancellationTokenSource.Cancel();
 753
 754            Sleep(10);
 755        }
 756
 757        private static void LogCanceled()
 758        {
 759            WriteLine($"Thread {CurrentThread.ManagedThreadId}:  Registered callback invoked.");
 760        }
 761
 762        private static void Counter(CancellationToken cancellationToken)
 763        {
 764            WriteLine($"Counter running on {CurrentThread.ManagedThreadId}.");
 765            while (!cancellationToken.IsCancellationRequested)
 766            {
 767                WriteLine($"Thread {CurrentThread.ManagedThreadId}:  {DateTime.Now.Ticks}");
 768                Sleep(1);
 769            }
 770
 771            WriteLine("Counter() was canceled");
 772        }
 773
 774        private static void CounterWithThrow(CancellationToken cancellationToken)
 775        {
 776            try
 777            {
 778                WriteLine($"CounterWithThrow running on {CurrentThread.ManagedThreadId}.");
 779                while (true)
 780                {
 781                    WriteLine($"Thread {CurrentThread.ManagedThreadId}:  {DateTime.Now.Ticks}");
 782                    cancellationToken.ThrowIfCancellationRequested();
 783                    Sleep(1);
 784                }
 785            }
 786            catch (OperationCanceledException e)
 787            {
 788                WriteLine($"Thread {CurrentThread.ManagedThreadId}: Caught OperationCanceledException: {e.Message}");
 789            }
 790        }
 791    }
 792```
 793
 794## Locking Primitives
 795
 796Let's assume we have many threads performing tasks, periodically they want to adjust some value to reflect their progress.
 797
 798``` csharp
 799using System;
 800using System.Threading;
 801
 802internal static class AggregatingFromManyThreadsIncorrect
 803{
 804    private static float _BALANCE;
 805
 806    private static void Main()
 807    {
 808        var threadCount = 10;
 809        var threads = new Thread[threadCount];
 810
 811        for (var i = 0; i < threadCount; i++)
 812        {
 813            var thread = new Thread(PerformTransactions);
 814            thread.Start();
 815            threads[i] = thread;
 816        }
 817
 818        for (var i = 0; i < threadCount; i++)
 819        {
 820            threads[i].Join();
 821        }
 822
 823        Console.WriteLine($"Balance = {_BALANCE}.");
 824    }
 825
 826    private static void PerformTransactions(object state)
 827    {
 828        for (var i = 0; i < 10000; i++)
 829        {
 830            _BALANCE += 1;
 831            Thread.Sleep(0);
 832            _BALANCE -= 1;
 833            Thread.Sleep(0);
 834        }
 835    }
 836}
 837```
 838
 839The above code adds and removed `1` from the balance.  This should mean that the final balance should be zero.  Run this a few times though and you'll see that the final value is regularly non-zero.  
 840
 841The reason for this is that the `+=` operator isn't atomic.  In fact the lines `+=` and `-=` are expanded into this:
 842
 843```csharp
 844private static void PerformTransactions(object state)
 845{
 846    for (var i = 0; i < 10000; i++)
 847    {
 848        var b1 = _BALANCE;
 849        var result1 = b1 + 1;
 850        _BALANCE = result1;
 851        Thread.Sleep(0);
 852        var b2 = _BALANCE;
 853        var result2 = b2 - 1;
 854        _BALANCE = result2;
 855        Thread.Sleep(0);
 856    }
 857}
 858```
 859
 860With many threads running it's quite probable that several threads will be in the code between assigning the current value of `_BALANCE` to the temporary variable and the code setting `_BALANCE` to the newly calculated result.
 861
 862The simplest was the make this thread safe is with the `lock` keyword.  The `lock` is a keyword that tells the compiler to wrap the locked block with a `Monitor.Enter()` and `Monitor.Exit()`.
 863
 864Rewrite the code above with the `lock` keyword.
 865
 866``` csharp
 867    using System;
 868    using System.Threading;
 869
 870    internal static class LockKeyword
 871    {
 872        private static int _BALANCE;
 873        private static readonly object LOCK = new object();
 874
 875        private static void Main()
 876        {
 877            var threadCount = 10;
 878            var threads = new Thread[threadCount];
 879
 880            for (var i = 0; i < threadCount; i++)
 881            {
 882                var thread = new Thread(PerformTransactions);
 883                thread.Start();
 884                threads[i] = thread;
 885            }
 886
 887            for (var i = 0; i < threadCount; i++) threads[i].Join();
 888
 889            Console.WriteLine($"Balance = {_BALANCE}.");
 890        }
 891
 892        private static void PerformTransactions(object state)
 893        {
 894            for (var i = 0; i < 10000; i++)
 895            {
 896                lock(LOCK)
 897                {
 898                    _BALANCE += 1;
 899                }
 900
 901                Thread.Sleep(0);
 902                lock(LOCK)
 903                {
 904                    _BALANCE -= 1;
 905                }
 906
 907                Thread.Sleep(0);
 908            }
 909        }
 910    }
 911```
 912
 913Running this again will prove that the result is now what we expect.  The `lock` block keyword is actually a shortcut for calling the `Monitor.Enter()` and `Monitor.Exit()` methods in a `try-finally` pattern (similar to how the `using` statement works).
 914
 915We can see this in the IL:
 916
 917``` il
 918.method private hidebysig static void  PerformTransactions(object state) cil managed
 919{
 920  // Code size       109 (0x6d)
 921  .maxstack  2
 922  .locals init (int32 V_0,
 923           object V_1,
 924           bool V_2)
 925  IL_0000:  ldc.i4.0
 926  IL_0001:  stloc.0
 927  IL_0002:  br.s       IL_0064
 928  IL_0004:  ldsfld     object BanksySan.Workshops.AdvancedCSharp.ThreadingExamples.LockKeyword::LOCK
 929  IL_0009:  stloc.1
 930  IL_000a:  ldc.i4.0
 931  IL_000b:  stloc.2
 932  .try
 933  {
 934    IL_000c:  ldloc.1
 935    IL_000d:  ldloca.s   V_2
 936    IL_000f:  call       void [mscorlib]System.Threading.Monitor::Enter(object,
 937                                                                        bool&)
 938    IL_0014:  ldsfld     int32 BanksySan.Workshops.AdvancedCSharp.ThreadingExamples.LockKeyword::_BALANCE
 939    IL_0019:  ldc.i4.1
 940    IL_001a:  add
 941    IL_001b:  stsfld     int32 BanksySan.Workshops.AdvancedCSharp.ThreadingExamples.LockKeyword::_BALANCE
 942    IL_0020:  leave.s    IL_002c
 943  }  // end .try
 944  finally
 945  {
 946    IL_0022:  ldloc.2
 947    IL_0023:  brfalse.s  IL_002b
 948    IL_0025:  ldloc.1
 949    IL_0026:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
 950    IL_002b:  endfinally
 951  }  // end handler
 952  IL_002c:  ldc.i4.0
 953  IL_002d:  call       void [mscorlib]System.Threading.Thread::Sleep(int32)
 954  IL_0032:  ldsfld     object BanksySan.Workshops.AdvancedCSharp.ThreadingExamples.LockKeyword::LOCK
 955  IL_0037:  stloc.1
 956  IL_0038:  ldc.i4.0
 957  IL_0039:  stloc.2
 958  .try
 959  {
 960    IL_003a:  ldloc.1
 961    IL_003b:  ldloca.s   V_2
 962    IL_003d:  call       void [mscorlib]System.Threading.Monitor::Enter(object,
 963                                                                        bool&)
 964    IL_0042:  ldsfld     int32 BanksySan.Workshops.AdvancedCSharp.ThreadingExamples.LockKeyword::_BALANCE
 965    IL_0047:  ldc.i4.1
 966    IL_0048:  sub
 967    IL_0049:  stsfld     int32 BanksySan.Workshops.AdvancedCSharp.ThreadingExamples.LockKeyword::_BALANCE
 968    IL_004e:  leave.s    IL_005a
 969  }  // end .try
 970  finally
 971  {
 972    IL_0050:  ldloc.2
 973    IL_0051:  brfalse.s  IL_0059
 974    IL_0053:  ldloc.1
 975    IL_0054:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
 976    IL_0059:  endfinally
 977  }  // end handler
 978  IL_005a:  ldc.i4.0
 979  IL_005b:  call       void [mscorlib]System.Threading.Thread::Sleep(int32)
 980  IL_0060:  ldloc.0
 981  IL_0061:  ldc.i4.1
 982  IL_0062:  add
 983  IL_0063:  stloc.0
 984  IL_0064:  ldloc.0
 985  IL_0065:  ldc.i4     0x2710
 986  IL_006a:  blt.s      IL_0004
 987  IL_006c:  ret
 988} // end of method LockKeyword::PerformTransactions
 989```
 990
 991We can see the `try` and `finally` and the calls to `Monitor.Enter()` and `Monitor.Exit()` within them.
 992
 993
 994## Performance
 995
 996Locking a code block, by necessity, causes a bottleneck because only one thread can get past that point at a time.  The performance hit, even for a very simple example like this a significant amount.  To combat this you need to limit the amount of code in a synchronised context to a minimum, or none at all.
 997
 998In the example above the nature of the calculation means that we don't actually need to be updating the shared value constantly.  Each thread can calculate it's total and just lock the shared value once at the end to update it.
 999
1000``` csharp
1001private static void PerformTransactions(object state)
1002{
1003    var balance = 0;
1004
1005    for (var i = 0; i < 10000; i++)
1006    {
1007        balance += 1;
1008        Thread.Sleep(0);
1009        balance-= 1;
1010        Thread.Sleep(0);
1011    }
1012
1013    lock (LOCK)
1014    {
1015        _BALANCE += balance;
1016    }
1017}
1018```
1019
1020Now the lock only occurs once per thread; a big improvement which is actually faster than the unsynchronised (and erroneous) example because theres no need for enforced atomic red/writes from the shared value.
1021
1022We can achieve this by using tasks.
1023
1024``` csharp
1025using System;
1026using System.Threading;
1027using System.Threading.Tasks;
1028
1029internal static class NoLockAtAll
1030{
1031    private static void Main()
1032    {
1033        var threadCount = 10;
1034        var tasks = new Task<int>[threadCount];
1035
1036        for (var i = 0; i < threadCount; i++) tasks[i] = new Task<int>(PerformTransactions);
1037
1038        for (var i = 0; i < tasks.Length; i++) tasks[i].Start();
1039
1040        var results = Task.WhenAll(tasks);
1041
1042        var balance = 0;
1043
1044        foreach (var result in results.Result) balance += result;
1045
1046        Console.WriteLine($"Balance = {balance}.");
1047    }
1048
1049    private static int PerformTransactions()
1050    {
1051        var balance = 0;
1052
1053        for (var i = 0; i < 10000; i++)
1054        {
1055            balance += 1;
1056            Thread.Sleep(0);
1057            balance -= 1;
1058            Thread.Sleep(0);
1059        }
1060
1061        return balance;
1062    }
1063}
1064```
1065
1066By re-writing the `PerformTransactions()` method so it returns a value and doesn't have any side-affects (i.e. changing any state external to it) we know have a method that is guaranteed thread safe.
1067
1068> This method also always returns the same value when given the same arguments (in this case no arguments).  When a method has all three of these properties we call it a _pure_ method.  Pure methods are one of the fundamental building blocks of functional programming.
1069
1070## Torn Reads
1071
1072The error we got in the initial multi-threaded balance calculation was due to the read, calculate and write not being atomic, however the read and write _independently_ are atomic.  There isn't any way that a read can happen _whilst_ a write is happening.  As per the ECMA specification:
1073
1074> I.12.6.5 Locks and threads
1075> > Built-in atomic reads and writes. All reads and writes of certain properly aligned data types are guaranteed to occur atomically
1076
1077The CLI guarantees that reads and writes to the following data types are atomic:
1078
1079* `bool`
1080* `char`
1081* `byte`
1082* `sbyte`
1083* `short`
1084* `ushort`
1085* `int`
1086* `uint`
1087* `float`
1088* Object pointer
1089
1090This doesn't include `double`, `decimal`, `long` or `ulong`.  This is because these types are all larger than 32-bits.
1091
1092The following code has one thread writing to a `ulong` and another reading from it.
1093
1094``` csharp
1095using System;
1096using System.Threading;
1097
1098internal static class TornReads
1099{
1100    private const ulong NUMBER_1 = 0xFFFFFFFFFFFFFFFF;
1101    private const ulong NUMBER_2 = 0x0000000000000000;
1102    private static ulong _NUMBER = NUMBER_1;
1103    private static bool @continue = true;
1104
1105    private static void Main()
1106    {
1107        var writerThread = new Thread(Writer);
1108        var readerThread = new Thread(Reader);
1109
1110        writerThread.Start();
1111        readerThread.Start();
1112        readerThread.Join();
1113        writerThread.Abort();
1114        @continue = true;
1115    }
1116
1117    private static void Reader()
1118    {
1119        for (var i = 0; i < 100; i++)
1120        {
1121            var number = _NUMBER;
1122            if (number != NUMBER_1 && number != NUMBER_2)
1123                Console.WriteLine($"{i,3}: Read: {number:X16} TornRead!");
1124            else
1125                Console.WriteLine($"{i,3}: Read: {number:X16}");
1126        }
1127    }
1128
1129    private static void Writer()
1130    {
1131        while (@continue)
1132        {
1133            _NUMBER = NUMBER_2;
1134            _NUMBER = NUMBER_1;
1135        }
1136    }
1137}
1138```
1139
1140A _good_ read would be either `0x0000000000000000` or `0xFFFFFFFFFFFFFFFF` (i.e. setting all bits to `0` or setting all bits to `1`).  A _bad_ read would be when some of the bits have been changed, but not all.
1141
1142Compile this code, **targeting x32**  and you will see that some values are `0x00000000FFFFFFFF` and some are `0xFFFFFFFF00000000`.  This is called a _torn read_.  We only see these two torn values because 32 bits are atomic, this it takes two atomic 32 bit writes to the full 62 bit value.
1143
1144Try targeting x64 now, you'll see that there aren't any torn reads at all.  This is because a x64 CPU can read and write 64 bits atomically.
1145
1146> NB The atomic reading of the values larger than 32 bits is because of the CPU architecture.  It's not guaranteed by the CLI.
1147
1148## Interlocked operations
1149
1150Interlocking is the term used for performing read _and_ write operations atomically.  C# has a static class `Interlocked` which has several methods to achieve this behaviour.  For example, the `Increment` and `Decrement` methods perform the `+=` and `-=` operations we used in the `lock` examples.
1151
1152Torn reads could be fixed with the `lock` keyword again, but `Interlocked` provides us with a better option using the `Read` method.
1153
1154## Volatile
1155
1156When compiling C# you have the option of optimising the code.  Effectively, optimising rewrites the code you've written so that it runs faster.  Production code should always be optimised.  
1157
1158> Because optimised code is different version it should have a different version number.
1159
1160Look at this code:
1161
1162``` csharp
1163using System;
1164using System.Threading;
1165
1166internal static class OptimisationBugs
1167{
1168    private const int TERMINATING_COUNT = 10;
1169    private static int _COUNT;
1170
1171    private static void Main()
1172    {
1173        var checker = new Thread(Checker);
1174        var stopper = new Thread(Counter);
1175        checker.Start();
1176        Thread.Sleep(10);
1177        stopper.Start();
1178        var timeout = TimeSpan.FromSeconds(1);
1179        _COUNT = 1;
1180        Console.WriteLine($"Waiting for {timeout} worker to stop.");
1181        checker.Join(timeout);
1182        if (checker.IsAlive)
1183        {
1184            Console.Error.WriteLine($"Thread failed to stop.  Aborting instead. ");
1185            checker.Abort();
1186        }
1187
1188        Console.WriteLine("Done");
1189    }
1190
1191    private static void Counter()
1192    {
1193        for (; _COUNT < 21; _COUNT++)
1194        {
1195            Console.WriteLine($"Count: {_COUNT}");
1196            if (_COUNT == TERMINATING_COUNT)
1197                Console.WriteLine($"Terminator {TERMINATING_COUNT} reached.");
1198        }
1199    }
1200
1201    private static void Checker()
1202    {
1203        var x = 0;
1204        while (_COUNT < TERMINATING_COUNT) x++;
1205        Console.WriteLine($"{nameof(Checker)} stopped at {x}.");
1206    }
1207}
1208```
1209
1210If we compile this code without any optimisations then things happen as we intend; that being that the `Checker` counts as high as it can before the `_COUNT` variable exceeds `9`.
1211
1212    Waiting for 00:00:01 worker to stop.
1213    Count: 1
1214    Count: 2
1215    Count: 3
1216    Count: 4
1217    Count: 5
1218    Count: 6
1219    Count: 7
1220    Count: 8
1221    Count: 9
1222    Count: 10
1223    Terminator 10 reached.
1224    Count: 11
1225    Count: 12
1226    Count: 13
1227    Count: 14
1228    Count: 15
1229    Count: 16
1230    Count: 17
1231    Count: 18
1232    Count: 19
1233    Count: 20
1234    Checker stopped at 9101073.
1235    Done
1236
1237Now compile this again, this time optimised.  Now the output is different, the timeout on the `Join` is breached.  If fact, if we didn't have the timeout there the application would never exit.
1238
1239To fix this problem we need to signal to the compiler that it need to fetch the value in `_COUNT` each time.  We need to mark the read as volatile.  We have two options here, we could put the `volatile` keyword in from of the declaration or we can use the static methods off the `Volatile` class.  The latter of these options is preferred for two reasons:
1240
12411. Using the `volatile` keyword causes every read and write to be volatile.
12421. Volatility is an operation of individual reads and writes, not of declaration.
1243
1244In this case it's the read that should be volatile, so we can correct the `Checker` method.
1245
1246``` csharp
1247private static void Checker()
1248{
1249    var x = 0;
1250    while (Volatile.Read(ref _COUNT) < TERMINATING_COUNT) x++;
1251    Console.WriteLine($"{nameof(Checker)} stopped at {x}.");
1252}
1253```
1254
1255Notice that the target of the read is passed by reference.