本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,記錄一下學習過程以備後續查用。
一、線程池基礎
首先,創建和銷燬線程是一個要耗費大量時間的過程,其次,太多的線程也會浪費內存資源,所以通過Thread類來創建過多的線程反而有損於性能。爲了改善這樣
的問題 ,.NET中就引入了線程池。
線程池形象的表示就是存放應用程序中使用的線程的一個集合(就是放線程的地方,這樣線程都放在一個地方就好管理了)。
CLR初始化時,線程池中是沒有線程的,在內部, 線程池維護了一個操作請求隊列。當應用程序想執行一個異步操作時,就調用一個方法,將一個任務放到線程池
的隊列中,線程池代碼從隊列中提取任務,將這個任務委派給一個線程池線程去執行,當線程池線程完成任務時,線程不會被銷燬,而是返回到線程池中,等待響應另
一個請求。由於線程不被銷燬, 這樣就可以避免因爲創建線程所產生的性能損失。
MSDN表述:
“線程池經常用在服務器應用程序中,每一個新進來的需求被分配給一個線程池中的線程,這樣該需求能被異步的執行,沒有阻礙主線程或推遲後繼需求的處理。”
注意:通過線程池創建的線程默認爲後臺線程,優先級默認爲Normal。
二、通過線程池的工作者線程實現異步
2.1創建工作者線程的方法
public static bool QueueUserWorkItem (WaitCallback callback);
public static bool QueueUserWorkItem(WaitCallback callback, Object state);
這兩個方法向線程池的隊列添加一個工作項(work item)以及一個可選的狀態數據,然後,這兩個方法就會立即返回。
工作項其實就是由callback參數標識的一個方法,該方法將由線程池線程執行。同時寫的回調方法必須匹配System.Threading.WaitCallback委託類型,定義爲:
public delegate void WaitCallback(Object state);
下面演示如何通過線程池線程來實現異步調用:
class Program
{
static void Main(string[] args)
{
#region 通過線程池的工作者線程實現異步
//設置線程池中工作者線程最大數量爲1000,I/O線程最大數量爲1000。
ThreadPool.SetMaxThreads(1000, 1000);
Console.WriteLine("Main thread: queue an asynchronous method.");
PrintMessage("Main thread start.");
//把工作項添加到隊列中,此時線程池會用工作者線程去執行回調方法。
ThreadPool.QueueUserWorkItem(AsyncMethod);
Console.Read();
#endregion
}
/// <summary>
/// 打印線程池信息
/// </summary>
/// <param name="data"></param>
private static void PrintMessage(string data)
{
//獲得線程池中可用的工作者線程數量及I/O線程數量
ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber);
Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workThreadNumber.ToString(),
ioThreadNumber.ToString());
}
/// <summary>
/// 異步方法:必須匹配WaitCallback委託
/// </summary>
/// <param name="state"></param>
private static void AsyncMethod(object state)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous method.");
Console.WriteLine("Asynchoronous thread has worked.");
}
}
運行結果如下:
從結果中可以看出,線程池中的可用的工作者線程少了一個,用去執行回調方法了。
ThreadPool.QueueUserWorkItem(WaitCallback callback,Object state) 方法可以把object對象作爲參數傳送到回調函數中,使用方法與
ThreadPool.QueueUserWorkItem(WaitCallback callback)類似,這裏就不列出了。
2.2 協作式取消
.NET Framework提供了取消操作的模式, 這個模式是協作式的。爲了取消一個操作,首先必須創建一個System.Threading.CancellationTokenSource對象。
下面代碼演示協作式取消的使用,主要實現當用戶在控制檯敲下回車鍵後就停止數數方法。
class Program
{
static void Main(string[] args)
{
#region 協作式取消
ThreadPool.SetMaxThreads(1000, 1000);
Console.WriteLine("Main thread run.");
PrintMessage("Start");
Run();
Console.ReadKey();
#endregion
}
/// <summary>
/// 打印線程池信息
/// </summary>
/// <param name="data"></param>
private static void PrintMessage(string data)
{
//獲得線程池中可用的工作者線程數量及I/O線程數量
ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber);
Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workThreadNumber.ToString(),
ioThreadNumber.ToString());
}
/// <summary>
/// 運行工作者線程(包含協作式取消)
/// </summary>
private static void Run()
{
CancellationTokenSource cts = new CancellationTokenSource();
//這裏是用Lambda表達式的寫法,效果一樣。
//ThreadPool.QueueUserWorkItem(obj => Count(cts.Token, 1000));
ThreadPool.QueueUserWorkItem(Callback, cts.Token);
Console.WriteLine("Press enter key to cancel the operation.\n");
Console.ReadLine();
//傳達取消請求
cts.Cancel();
}
/// <summary>
/// 回調函數
/// </summary>
/// <param name="state"></param>
private static void Callback(object state)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous method start.");
CancellationToken token = (CancellationToken)state;
Count(token, 1000);
}
/// <summary>
/// 數數
/// </summary>
/// <param name="token"></param>
/// <param name="countTo"></param>
private static void Count(CancellationToken token, int countTo)
{
for (int i = 1; i <= countTo; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Count is canceled.");
break;
}
Console.WriteLine(i);
Thread.Sleep(300);
}
Console.WriteLine("Count has done.");
}
}
運行結果如下:
三、使用委託實現異步
涉及術語解釋--異步編程模型:
APM 異步編程模型(Asynchronous Programming Model)
EAP 基於事件的異步編程模式(Event-based Asynchronous Pattern)
TAP 基於任務的異步編程模式(Task-based Asynchronous Pattern)
通過調用ThreadPool的QueueUserWorkItem方法來來啓動工作者線程非常方便,但委託WaitCallback指向的是帶有一個參數的無返回值的方法。如果我們實際操作中
需要有返回值,或者需要帶有多個參數, 這時通過這樣的方式就難以實現了。 爲了解決這樣的問題,我們可以通過委託來建立工作這線程。
下面代碼演示使用委託實現異步:
class Program
{
//使用委託實現異步,是使用了異步編程模型APM。
private delegate string ThreadDelegate();
static void Main(string[] args)
{
#region 使用委託實現異步
ThreadPool.SetMaxThreads(1000, 1000);
PrintMessage("Main thread start.");
//實例化委託
ThreadDelegate threadDelegate = new ThreadDelegate(AsyncMethod);
//異步調用委託
IAsyncResult result = threadDelegate.BeginInvoke(null, null);
//獲取結果並打印
string returnData = threadDelegate.EndInvoke(result);
Console.WriteLine(returnData);
Console.ReadLine();
#endregion
}
/// <summary>
/// 打印線程池信息
/// </summary>
/// <param name="data"></param>
private static void PrintMessage(string data)
{
//獲得線程池中可用的工作者線程數量及I/O線程數量
ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber);
Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workThreadNumber.ToString(),
ioThreadNumber.ToString());
}
/// <summary>
/// 異步方法
/// </summary>
/// <returns></returns>
private static string AsyncMethod()
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous method.");
return "Method has completed.";
}
}
運行結果如下:
四、任務
同樣,任務的引入也是爲了解決通過ThreadPool.QueueUserWorkItem中限制的問題。
4.1 使用任務來實現異步
class Program
{
static void Main(string[] args)
{
#region 使用任務實現異步
ThreadPool.SetMaxThreads(1000, 1000);
PrintMessage("Main thread start.");
//調用構造函數創建Task對象
Task<int> task = new Task<int>(n => AsyncMethod((int)n), 10);
//啓動任務
task.Start();
//等待任務完成
task.Wait();
Console.WriteLine("The method result is: " + task.Result);
Console.ReadLine();
#endregion
}
/// <summary>
/// 打印線程池信息
/// </summary>
/// <param name="data"></param>
private static void PrintMessage(string data)
{
//獲得線程池中可用的工作者線程數量及I/O線程數量
ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber);
Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workThreadNumber.ToString(),
ioThreadNumber.ToString());
}
/// <summary>
/// 異步方法
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
private static int AsyncMethod(int n)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous method.");
int sum = 0;
for (int i = 1; i < n; i++)
{
//運算溢出檢查
checked
{
sum += i;
}
}
return sum;
}
}
運行結果如下:
4.2 取消任務
如果要取消任務, 同樣也可以CancellationTokenSource對象來取消。
下面代碼演示取消一個任務:
class Program
{
static void Main(string[] args)
{
#region 取消任務
ThreadPool.SetMaxThreads(1000, 1000);
PrintMessage("Main thread start.");
CancellationTokenSource cts = new CancellationTokenSource();
//調用構造函數創建Task對象,將一個CancellationToken傳給Task構造器從而使Task和CancellationToken關聯起來。
Task<int> task = new Task<int>(n => AsyncMethod(cts.Token, (int)n), 10);
//啓動任務
task.Start();
//延遲取消任務
Thread.Sleep(3000);
//取消任務
cts.Cancel();
Console.WriteLine("The method result is: " + task.Result);
Console.ReadLine();
#endregion
}
/// <summary>
/// 打印線程池信息
/// </summary>
/// <param name="data"></param>
private static void PrintMessage(string data)
{
//獲得線程池中可用的工作者線程數量及I/O線程數量
ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber);
Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workThreadNumber.ToString(),
ioThreadNumber.ToString());
}
/// <summary>
/// 異步方法
/// </summary>
/// <param name="ct"></param>
/// <param name="n"></param>
/// <returns></returns>
private static int AsyncMethod(CancellationToken ct, int n)
{
Thread.Sleep(1000);
PrintMessage("Asynchoronous method.");
int sum = 0;
try
{
for (int i = 1; i < n; i++)
{
//當CancellationTokenSource對象調用Cancel方法時,就會引起OperationCanceledException異常,
//通過調用CancellationToken的ThrowIfCancellationRequested方法來定時檢查操作是否已經取消,
//這個方法和CancellationToken的IsCancellationRequested屬性類似。
ct.ThrowIfCancellationRequested();
Thread.Sleep(500);
//運算溢出檢查
checked
{
sum += i;
}
}
}
catch (Exception e)
{
Console.WriteLine("Exception is:" + e.GetType().Name);
Console.WriteLine("Operation is canceled.");
}
return sum;
}
}
運算結果如下:
4.3 使用任務工廠實現異步操作
同樣也可以通過任務工廠TaskFactory類型來實現異步操作。
class Program
{
static void Main(string[] args)
{
#region 使用任務工廠實現異步
ThreadPool.SetMaxThreads(1000, 1000);
Task.Factory.StartNew(() => PrintMessage("Main thread."));
Console.Read();
#endregion
}
/// <summary>
/// 打印線程池信息
/// </summary>
/// <param name="data"></param>
private static void PrintMessage(string data)
{
//獲得線程池中可用的工作者線程數量及I/O線程數量
ThreadPool.GetAvailableThreads(out int workThreadNumber, out int ioThreadNumber);
Console.WriteLine("{0}\n CurrentThreadId is:{1}\n CurrentThread is background:{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is:{4}\n",
data,
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.IsBackground.ToString(),
workThreadNumber.ToString(),
ioThreadNumber.ToString());
}
}
運行結果如下: