Foreground-потоки могут предотвратить завершение работы программы
Свойство isBackground класса Thread определяет является ли поток фоновым(background). CLR завершает работу программы если остаются только фоновые потоки. Другими словами, пока есть хотя бы один не-фоновый поток CLR будет продолжать выполнение.
Если в своём приложении создать поток и сделать его foreground(по умолчанию), и выйти из приложения(выйдя из метода Main или нажав на кнопку завершения работы) то приложении будет продолжать работу до тех пор, пока этот поток выполняется. Но если сделать поток фоновым, то приложение завершит свою работу как раз тогда, когда этого от него и ожидают.
Нижеприведённый код иллюстрирует эту ситуацию:
using System;
using System.Threading;
namespace Background
{
class Test
{
private static void Worker()
{
Thread.Sleep(5000);
Console.WriteLine("worker done at {0}",
DateTime.Now.ToLongTimeString());
}
[STAThread]
static void Main(string[] args)
{
Thread workerThread = new Thread(new ThreadStart(Worker));
//workerThread.IsBackground = true;
workerThread.Start();
Console.WriteLine("Main done at {0}",
DateTime.Now.ToLongTimeString());
}
}
}
В этом примере создаётся поток, который ждёт 5 секунд. За это время метод Main завершает свою работу, но программа продолжает выполнение. Она завершит свою работу только тогда, когда закончит свою работу второй поток:

Если раскомментировать workerThread.IsBackground = true, то программа завершает работу при выходе из метода Main:

Фоновые потоки могут вызвать проблемы при завершении
У фоновых потоков есть очевидное преимущество – они выполняются до тех пор, пока выполняется основной поток. Но если фоновый поток использует некоторые важные ресурсы, то необходимо быть уверенным, что он корректно завершает свою работу.
Цитата с MSDN:”Когда среда CLR останавливает фоновые потоки после того, как все потоки переднего плана в управляемом исполняемом файле завершились, метод Thread..::.Abort не используется. Следовательно, нельзя использовать перехват исключения ThreadAbortException для обнаружения прерывания фоновых потоков средой CLR.” (ссылка)
Следующий код иллюстрирует это:
using System;
using System.Threading;
namespace BackgroundThreadAndAbort
{
class Test
{
private static void Worker()
{
Console.WriteLine("Worker started... given chance to cleanup?");
try
{
Thread.Sleep(5000);
}
catch(ThreadAbortException)
{
Console.WriteLine("Thread aborted exception received");
}
}
[STAThread]
static void Main(string[] args)
{
Thread workerThread = new Thread(new ThreadStart(Worker));
workerThread.IsBackground = true;
workerThread.Start();
Thread.Sleep(2000);
Console.WriteLine("Main done");
}
}
}
В этой программе фоновый поток выполняет метод Worker. В этом методе ожидается исключение ThreadAbortException, которое должно быть обработано. Однако ислючение ThreadAbortException не выдаётся:

ThreadAbortException
При вызове метода Abort для уничтожения потока, общеязыковая среда выполнения выдает исключение ThreadAbortException. Исключение ThreadAbortException является особым исключением, которое может быть перехвачено, но снова возникает в конце блока catch. При возникновении этого исключения среда выполнения перед тем, как завершить поток, выполняет все блоки finally. Так как поток может выполнять код в блоках finally неограниченное время или вызывать метод Thread..::.ResetAbort для отмены аварийного завершения потока, нет гарантии, что поток когда-либо закончит работу. Чтобы дождаться окончания работы прерванного потока можно вызвать метод Thread..::.Join.
using System;
using System.Threading;
namespace ThreadAborting
{
class Test
{
private static void Worker()
{
try
{
try
{
Thread.Sleep(5000);
}
catch (ThreadAbortException)
{
Console.WriteLine("ThreadAbortException caught");
}
Console.WriteLine("Leaving the method");
}
finally
{
Console.WriteLine("In the finally block");
}
}
[STAThread]
static void Main(string[] args)
{
Thread workerThread = new Thread(new ThreadStart(Worker));
workerThread.Start();
Thread.Sleep(1000);
Console.WriteLine("Calling abort");
workerThread.Abort();
Thread.Sleep(1000);
Console.WriteLine("Main done");
}
}
}

В этой программе Main вызывает метод Abort. Соответствующий поток получает исключение ThreadAbortException. Это исключение обрабатывается в блоке try-catch. В блоке catch нет вызова throw, и исключение выдаётся автоматически. В результате вместо “Leaving the method” на экране появляется “In the finally block”.
ResetAbort() приводит к неожиданным сюрпризам
Класс Thread предоставляет метод ResetAbort() для отмены метода Abort(). Пример использования этого метода:
using System;
using System.Threading;
namespace ResetAbort
{
class Test
{
private static void Worker()
{
try
{
Thread.Sleep(5000);
}
catch (ThreadAbortException)
{
Console.WriteLine("ThreadAbortException caught");
Thread.ResetAbort();
}
Console.WriteLine("Working again!");
Thread.Sleep(10000);
}
[STAThread]
static void Main(string[] args)
{
Thread workerThread
= new Thread(new ThreadStart(Worker));
workerThread.Start();
Thread.Sleep(1000);
Console.WriteLine("Calling abort");
workerThread.Abort();
Thread.Sleep(2000);
Console.WriteLine("Main done");
}
}
}
Когда CLR выдаёт исключение ThreadAbortException в блоке catch вызывается метод ResetAbort(). Он “отменяет” Abort() и метод Worker продолжает выполнение:

Проблема здесь заключается в том, что код, который вызвал метод Abort() никак не может узнать что поток на самом деле продолжает работу. Это может привести к неопределённым последствиям. Поэтому не стоит использовать метод ResetAbort() без крайней необходимости.
Синхронизация и статические члены
Предположим у вас есть статический метод, и необходимо сделать так, чтобы к нему имел доступ только один поток одновременно. Обычно эта задача решается с помощью критической секции(оператора lock) на объекте. Но в случае со статическим полем/методом это не работает по одной простой причине: статические методы независимы от объекта. Следующий пример иллюстрирует эту ситуацию:
Counter.cs:
using System;
using System.Threading;
namespace SynchOnType
{
public class Counter
{
private static int value;
private static void IncreaseCount()
{
Console.WriteLine("IncreaseCount called by {0} at {1}",
AppDomain.GetCurrentThreadId(),
DateTime.Now.ToLongTimeString());
value++;
Thread.Sleep(2000);
}
public Counter()
{
lock (this)
{
IncreaseCount();
}
}
}
}
Test.cs
using System;
using System.Threading;
namespace SynchOnType
{
class Test
{
private static void Worker()
{
Console.WriteLine("In thread {0}", AppDomain.GetCurrentThreadId());
Counter _counter = new Counter();
}
[STAThread]
static void Main(string[] args)
{
Thread thread1 = new Thread(new ThreadStart(Worker));
Thread thread2 = new Thread(new ThreadStart(Worker));
thread1.Start();
thread2.Start();
}
}
}
Если запустить этот код то получится слудеющее:

В этом примере 2 потока создают объект Counter почти одновременно. В конструкторе вызывается метод IncreaseCount, который требует синхронизации и устанавливается блокировка на экземпляр объекта. Как видно из результатов работы синхронизация не выполняется – ни один из потоков не останавливается на 2 секунды.
Чтобы два потока могли использовать для синхронизации мьютекс им нужен один экземпляр объекта. Поэтому в данном случае для синхронизации нужно использовать метаданные класса Counter:
public Counter()
{
lock (GetType())
{
IncreaseCount();
}
}
Общий для 2-х потоков объект можно получить вызвав метод GetType соответствующего экземпляра:

В результате 2 потока выполняют метод IncreaseCount() с промежутком в 2 секунды, как и должно быть, что доказывает то, что вызовы метода были синхронизированы.
