Руководство по программированию многопоточности C#

1- The concept of multithreading

Многопоточность это важное понятия в языке программирования, так же как и C#, это способ создать параллельную обработку. Для ясности взгляните на следующий пример:
HelloThread.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class HelloThread
    {
        public static void Main(string[] args)
        {
             
            Console.WriteLine("Create new Thread...\n");          

            // Creates a child thread, which runs in parallel with the main thread.
            Thread newThread = new Thread(WriteB);

            Console.WriteLine("Start newThread...\n");

            // Start the thread
            newThread.Start();

            Console.WriteLine("Call Write('-') in main Thread...\n");

            // In the main thread print out character '-'
            for (int i = 0; i < 50; i++)
            {
                Console.Write('-');

                // Sleep 70 millisenconds
                Thread.Sleep(70);
            }


            Console.WriteLine("Main Thread finished!\n");
            Console.Read();
        }



        public static void WriteB()
        {
            // Print out the 'B' 100 times.
            for (int i = 0; i < 100; i++)
            {
                Console.Write('B');

                // Sleep 100 millisenconds
                Thread.Sleep(100);
            }
                
        }
    }

   
}
Запускаете следующий класс:
Правило работы потока (Thread) объясняется в следующей иллюстрации:

2- Pass parameters to Thread

Перед этим вы ознакомились с примером  HelloThread, вы создали упакованный объект (wrap) статический метод выполнения данного метода параллельно с главным потоком (thread).
Статический метод может стать параметром переданным в Constructor класса  Thread если метод не имеет параметров, или единственный параметр вида объекта  object.
// A static method, have no parameters
public static void LetGo()
{
     // Do something here
}

// A static method has a unique parameter, and the type is object.
public static void GetGo(object value)
{
      // Làm gì đó ở đây.
}
В следующем примере, я создам один  Thread чтобы упаковать статичский метод с одним параметром (object type). Запускаю thread и передаю значение параметру.
MyWork.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class MyWork
    {

        public static void DoWork(object ch)
        {
            for (int i = 0; i < 100; i++)
            {
                // Write to console
                Console.Write(ch);

                // Sleep 50 milliseconds
                Thread.Sleep(50);
            }
        }



    }
}
ThreadParamDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadParamDemo
    {

        public static void Main(string[] args)
        {

            Console.WriteLine("Create new thread.. \n");

            // Create a Thread object to wrap around the static method MyWork.DoWork
            Thread workThread = new Thread(MyWork.DoWork);

            Console.WriteLine("Start workThread...\n");

            // Run workThread,
            // and pass parameter to MyWork.DoWork.
            workThread.Start("*");


            for (int i = 0; i < 20; i++)
            {
                Console.Write(".");

                // Sleep 30 milliseconds.
                Thread.Sleep(30);
            }

            Console.WriteLine("MainThread ends");
            Console.Read();
        }
    }

}
Запуск класса  ThreadParamDemo:

3- Thread uses non-static method

Вы так же можете создать поток (thread) используя стандартные методы. Например:
Worker.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class Worker
    {
        private string name;
        private int loop;

        public Worker(string name, int loop)
        {
            this.name = name;
            this.loop = loop;
        }

        public void DoWork(object value)
        {
            for (int i = 0; i < loop; i++)
            {
                Console.WriteLine(name + " working " + value);
                Thread.Sleep(50);
            }
             
        }

    }

}
WorkerTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class WorkerTest
    {

        public static void Main(string[] args)
        {
            Worker w1 = new Worker("Tran",10);

            // Create a Thread object.
            Thread workerThread1 = new Thread(w1.DoWork);

            // Pass parameter to DoWork method.
            workerThread1.Start("A");


            Worker w2 = new Worker("Marry",15);

            // Create a Thread object.
            Thread workerThread2 = new Thread(w2.DoWork);

            // Pass parameter to DoWork method.
            workerThread2.Start("B");
          
            Console.Read();
        }
    }

}
Запуск примера:

4- ThreadStart Delegate

ThreadStart это класс (Delegate), инциируется методом упаковки. Передан как параметро для инициализации объетка Thread.
 С  .Net < 2.0, чтобы стартовать (start) один поток (thread), вам нужно создать  ThreadStart, он является delegate.

Начиная с версии 2.0  .NET Framework, не нужно создавать отдельный делегат ( ThreadStart). Вам нужно только указать имя метода в constructor у  Thread.
 
Programmer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class Programmer
    {
        private string name;
        public Programmer(string name)  
        {
            this.name= name;
        }

        // This is none static method, no parameters.
        public void DoCode()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(name + " codding ... ");
                Thread.Sleep(50);
            }
                
        }
    }

}
ThreadStartDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;


namespace MultithreadingTutorial
{
    class ThreadStartDemo
    {


        public static void Main(string[] args)
        {
            // Create a ThreadStart object that wrap a static method.
            // (It can only wrap method have no parameters)
            // (ThreadStart is a delegate).      
            ThreadStart threadStart1 = new ThreadStart(DoWork);

            // Create a thread wrap threadStart1.
            Thread workThread = new Thread(threadStart1);

            // Start thread
            workThread.Start();

            // Create Programmer object.
            Programmer tran = new Programmer("Tran");

            // You can also create ThreadStart objects that wrap a non-static method.
            // (It can only wrap method have no parameters)
            ThreadStart threadStart2 = new ThreadStart(tran.DoCode);

            // Create a Thread wrap threadStart2.
            Thread progThread = new Thread(threadStart2);

            progThread.Start();

            Console.WriteLine("Main thread ends");
            Console.Read();
        }


        public static void DoWork()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("*");
                Thread.Sleep(100);
            }                
        }
    }

}
Запуск примера:

5- Thread with anonymous code

Выше вы создали потоки  Thread используя определенный метод. Вы можете создать поток чтобы выполнить любой код.
// Use delegate() to create anonymous method.
delegate()
{
     // Do something here.
}
Пример:
ThreadUsingSnippetCode.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadUsingSnippetCode
    {

        public static void Main(string[] args)
        {

            Console.WriteLine("Create thread 1");

            // Creates a thread to execute a snippet code.
            Thread newThread1 = new Thread(
                delegate()
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Code in delegate() " + i);
                        Thread.Sleep(50);
                    }

                }
            );

            Console.WriteLine("Start newThread1");

            // Start thread.
            newThread1.Start();

            Console.WriteLine("Create thread 2");

            // Creates a thread to execute a snippet code.
            Thread newThread2 = new Thread(
                delegate(object value)
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("Code in delegate(object) " + i + " - " + value);
                        Thread.Sleep(100);
                    }

                }
            );

            Console.WriteLine("Start newThread2");

            // Start thread 2.
            // Pass parameter to delegate().
            newThread2.Start("!!!");


            Console.WriteLine("Main thread ends");
            Console.Read();

        }
    }

}
Запуск примера:

6- Name the thread

В многопоточном программировании вы можете именовать потоки (thread), это действительно  полезно в случае отладки (Debugging), чтобы узнать в каком местоположении кода выполняется какой поток.

В одном потоке thread вы можете назвать  Thread.CurrentThread.Name чтобы получить имя потока, выполняющего на тот момент.

Пример по иллюстрации:
NamingThreadDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class NamingThreadDemo
    {

        public static void Main(string[] args)
        {
            // Set name to current thread
            // (Main thread)
            Thread.CurrentThread.Name = "Main";

            Console.WriteLine("Code of "+ Thread.CurrentThread.Name);

            Console.WriteLine("Create new thread");

            // Create a thread
            Thread letgoThread = new Thread(LetGo);

            // Set name to thread    
            letgoThread.Name = "Let's Go";

            letgoThread.Start();

            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Code of " + Thread.CurrentThread.Name);
                Thread.Sleep(30);
            }

            Console.Read();
        }


        public static void LetGo()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Code of " + Thread.CurrentThread.Name);
                Thread.Sleep(50);
            }
        }
    }

}
Запуск примера:

7- Priority between threads

В C# есть 5 уровней приоритета потока, они определяются в enum ThreadPriority.
** ThreadPriority enum **
enum ThreadPriority {
    Lowest,
    BelowNormal,
    Normal,
    AboveNormal,
    Highest
}
Обычно с выскоскоростным компьютером, если потоки выполняют малую работу, вам очень сложно определить разницу между потоками выского приоритета и низкого приоритета.

Пример ниже имеет 2 потока, каждый поток печатает 100 тысяч строк (Достаточно большое количество, чтобы увидеть разницу). 
ThreadPriorityDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadPriorityDemo
    {
        private static DateTime endsDateTime1;
        private static DateTime endsDateTime2;


        public static void Main(string[] args)
        {
            endsDateTime1 = DateTime.Now;
            endsDateTime2 = DateTime.Now;

            Thread thread1 = new Thread(Hello1);

            // Set highest priority for thread1
            thread1.Priority = ThreadPriority.Highest;

            Thread thread2 = new Thread(Hello2);

            // Set the lowest priority for thread2.
            thread2.Priority = ThreadPriority.Lowest;


            thread2.Start(); thread1.Start();

            Console.Read();
        }


        public static void Hello1()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("Hello from thread 1: "+ i);
            }
            // The time of thread1 ends.     
            endsDateTime1 = DateTime.Now;

            PrintInterval();
        }

        public static void Hello2()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("Hello from thread 2: "+ i);
            }
            // The time of thread2 ends.
            endsDateTime2 = DateTime.Now;

            PrintInterval();
        }

        private static void PrintInterval()
        {
            // Interval (Milliseconds)
            TimeSpan interval = endsDateTime2 - endsDateTime1;

 
            Console.WriteLine("Thread2 - Thread1 = " + interval.TotalMilliseconds + " milliseconds");
        }
    }

}
Запуск примера:

8- Using Join()

Thread.Join() это метод оповещения об ожидании завершении данного потока перед тем, как продолжить запуск главный поток.
ThreadJoinDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;


namespace MultithreadingTutorial
{
    class ThreadJoinDemo
    {

        public static void Main(string[] args)
        {
            Console.WriteLine("Create new thread");

            Thread letgoThread = new Thread(LetGo);
            
            // Start Thread.       
            letgoThread.Start();

            // Tell to the parent thread (here is main thread)
            // wait for the letgoThread to finish, then continue running.
            letgoThread.Join();
            
            // This statement must wait for letgoThread to end, then continue running
            Console.WriteLine("Main thread ends");
            Console.Read();
        }


        public static void LetGo()
        {
            for (int i = 0; i < 15; i++)
            {
                Console.WriteLine("Let's Go " + i);
            }
        }
    }

}
Запуск примера:

9- Using Yield()

Теоритически, "Yield" означает отпустить, сдаться. Поток  Yield говорит системе, что он готов дать другим потокам потокам встать на свои места. Это означает что поток не делает что-то критически важное. Заметьте, что это подсказка, несмотря на то, что нет гарантии эффекта. 
Так что метод  Yield()  используется когда вы видите что поток свободен, он не делает ничего важного, он подсказывает операционной системе дать временный приоритет другим потокам.
В следующем примере, имеется 2 потока, каждый поток печатает одну строку 100 тысяч раз (достаточно большое количество, чтобы увидеть разницу). Одному потоку дан самый высокий уровень приоритета и другому самый низкий уровень приоритета. Измерить время окончания 2-х потоков.
ThreadYieldDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MultithreadingTutorial
{
    class ThreadYieldDemo
    {

        private static DateTime importantEndTime;
        private static DateTime unImportantEndTime;

        public static void Main(string[] args)
        {
            importantEndTime = DateTime.Now;
            unImportantEndTime = DateTime.Now;

            Console.WriteLine("Create thread 1");

            Thread importantThread = new Thread(ImportantWork);
            // Set the highest priority for this thread.
            importantThread.Priority = ThreadPriority.Highest;

            Console.WriteLine("Create thread 2");

            Thread unImportantThread = new Thread(UnImportantWork);
            // Set the lowest priority for this thread.
            unImportantThread.Priority = ThreadPriority.Lowest;

            // Start threads.
            unImportantThread.Start();
            importantThread.Start();
           

            Console.Read();

        }

        // A important job.
        public static void ImportantWork()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("\n Important work " + i);

                // Notifying the operating system,
                // this thread gives priority to other threads.
                Thread.Yield();
            }
            // The end time of this thread.
            importantEndTime = DateTime.Now;
            PrintTime();
        }

        public static void UnImportantWork()
        {
            for (int i = 0; i < 100000; i++)
            {
                Console.WriteLine("\n  -- UnImportant work " + i);
            }
            // The end time of this thread.      
            unImportantEndTime = DateTime.Now;
            PrintTime();
        }

        private static void PrintTime()
        {
            // Interval (Milliseconds)
            TimeSpan interval = unImportantEndTime - importantEndTime;
      
            Console.WriteLine("UnImportant Thread - Important Thread = " + interval.TotalMilliseconds +" milliseconds");
        }
         
    }

}
Запуск класса в случае отсутствия  Thread.Yield():
Запуск класса в случае поток с высоким приоритетом постоянно вызывает  Thread.Yield() чтобы потребовать систему временном дать приоритет другому потоку.

10- TODO