1. 概述
1.1 关于线程池的理解
①线程池中的线程都是后台线程
②创建线程的开销是巨大的,因此,非必要情况下,应谨慎创建线程,以此减少线程的创建和销毁。一般情况下,应在线程池中选择线程来执行短暂的异步操作
③通过System.Threading.ThreadPool类型可以使用线程池
④线程池是受.NET通用语言运行时(Common Language Runtime,简称CLR)管理的。这意味着每个CLR都有一个线程池实例
⑤线程池创建线程的数量是有限制的,若线程执行较长时间的操作,就回导致线程池放入新的操作时,线程池会不断创建新的线程,当线程池达到一定数量后,放入线程池的操作在无空闲线程时,只能在队列中等待直到线程池中的工作线程有能力来执行。当停止向线程池中放置新操作时,线程池最终会删除一定时间后过期的不再使用的线程。
⑥ASP.NET基础设施使用自己的线程池,如果在线程池中浪费所有的工作者线程,Web服务器将不能够服务新的请求。在ASP.NET中只推荐使用输入/输出密集型的异步操作,因为其使用了一个不同的方式,叫做I/O线程。
1.2 使用线程池的作用
①线程池中的线程一般用于处理运行时间短的操作
②使用线程池可以减少并行度耗费及节省操作系统资源
③实现批量处理操作
2. 在线程池中调用委托
using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace Chapter3.Recipe1
{
class Program
{
static void Main(string[] args)
{
int threadId = 0;
RunOnThreadPool poolDelegate = Test;
var t = new Thread(() => Test(out threadId));
t.Start();
t.Join();
WriteLine($"Thread id: {threadId}");
//BeginInvoke方法接受一个回调函数。该回调函数在异步操作完成后会被调用,
//并且一个用户自定义的状态会传给该回调函数【该状态通常用于区分异步调用】。
IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "a delegate asynchronous call");
r.AsyncWaitHandle.WaitOne();
//EndInvoke方法会等待异步操作完成
//当操作完成后,传递给BeginInvoke方法的回调函数将被放置到线程池中,确切地说是一个工作者线程中
string result = poolDelegate.EndInvoke(out threadId, r);
WriteLine($"Thread pool worker thread id: {threadId}");
WriteLine(result);
//如果注释掉Thread.Sleep方法调用,
//回调函数将不会被执行。这是因为当主线程完成后,所有的后台线程会被停止,包括该回调函数
Sleep(TimeSpan.FromSeconds(2));
//使用BeginOperationName/EndOperationName方法和.NET中的IAsyncResult对象等方式被称为异步编程模型(或APM模式),
//这样的方法对称为异步方法
Console.ReadKey();
}
//声明一个委托
private delegate string RunOnThreadPool(out int threadId);
/// <summary>
/// 定义回调函数
/// </summary>
/// <param name="ar"></param>
private static void Callback(IAsyncResult ar)
{
WriteLine("Starting a callback...");
WriteLine($"State passed to a callbak: {ar.AsyncState}");
WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}");
WriteLine($"Thread pool worker thread id: {CurrentThread.ManagedThreadId}");
}
private static string Test(out int threadId)
{
WriteLine("Starting...");
WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}");
Sleep(TimeSpan.FromSeconds(2));
threadId = CurrentThread.ManagedThreadId;
return $"Thread pool worker thread id was: {threadId}";
}
}
/**
* Starting...
Is thread pool thread: False
Thread id: 3
Starting...
Is thread pool thread: True
Thread pool worker thread id: 4
Thread pool worker thread id was: 4
Starting a callback...
State passed to a callbak: a delegate asynchronous call
Is thread pool thread: True
Thread pool worker thread id: 4
* **/
}
3. 向线程池中放入异步操作ThreadPool.QueueUserWorkItem 方法
using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace Chapter3.Recipe2
{
class Program
{
static void Main(string[] args)
{
const int x = 1;
const int y = 2;
const string lambdaState = "lambda state 2";
//QueueUser-WorkItem方法将该方法放到线程池中
ThreadPool.QueueUserWorkItem(AsyncOperation);
Sleep(TimeSpan.FromSeconds(1));
ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
Sleep(TimeSpan.FromSeconds(1));
ThreadPool.QueueUserWorkItem( state =>
{
WriteLine($"Operation state: {state}");
WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
Sleep(TimeSpan.FromSeconds(2));
}, "lambda state");
ThreadPool.QueueUserWorkItem( _ =>
{
WriteLine($"Operation state: {x + y}, {lambdaState}");
WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
Sleep(TimeSpan.FromSeconds(2));
}, "lambda state");
Sleep(TimeSpan.FromSeconds(2));
Console.ReadKey();
/**
* 在操作完成后让线程睡眠一秒钟,从而让线程池拥有为新操作重用线程的可能性。
* 如果注释掉所有的Thread.Sleep调用,那么所有打印出的线程ID多半是不一样的。
* 如果ID是一样的,那很可能是前两个线程被重用来运行接下来的两个操作
* **/
}
private static void AsyncOperation(object state)
{
WriteLine($"Operation state: {state ?? "(null)"}");
WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}");
Sleep(TimeSpan.FromSeconds(2));
}
}
}
4. 在线程池中使用等待事件处理器及超时
using System;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;
namespace Chapter3.Recipe5
{
class Program
{
static void Main(string[] args)
{
RunOperations(TimeSpan.FromSeconds(5));
RunOperations(TimeSpan.FromSeconds(7));
Console.ReadKey();
}
static void RunOperations(TimeSpan workerOperationTimeout)
{
using (var evt = new ManualResetEvent(false))
using (var cts = new CancellationTokenSource())
{
WriteLine("Registering timeout operation...");
//ThreadPool.RegisterWaitForSingleObject。该方法允许将回调函数放入线程池中的队列中
//当提供的等待事件处理器收到信号或发生超时时,该回调函数将被调用
var worker = ThreadPool.RegisterWaitForSingleObject(evt
, (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut)
, null
, workerOperationTimeout
, true);
WriteLine("Starting long running operation...");
ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt));
Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2)));
worker.Unregister(evt);
}
}
static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
{
for(int i = 0; i < 6; i++)
{
if (token.IsCancellationRequested)
{
return;
}
Sleep(TimeSpan.FromSeconds(1));
}
evt.Set();
}
static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
{
if (isTimedOut)
{
Console.WriteLine("超时...");
cts.Cancel();
WriteLine("Worker operation timed out and was canceled.");
}
else
{
WriteLine("Worker operation succeded.");
}
}
}
}
当有大量的线程必须处于阻塞状态中等待一些多线程事件发信号时,以上方式非常有用。借助于线程池的基础设施,我们无需阻塞所有这样的线程。可以释放这些线程直到信号事件被设置。在服务器端应用程序中这是个非常重要的应用场景,因为服务器端应用程序要求高伸缩性及高性能