从 .NET Framework 4 开始,使用多线程的推荐方法是使用任务并行库 (TPL) 和并行 LINQ (PLINQ)。
任务并行库(TPL)说的是 System.Threading 和 System.Threading.Tasks 空间中的一组公共类型和 API。较为常见的就是Thread、ThreadPool、Task等
LINQ (PLINQ) 是语言集成查询 (LINQ) 模式的并行实现,可以理解为对LINQ的一些扩充方法,类似于“IEnumerable .AsParallel()”方法。
创建和控制线程,设置其优先级并获取其状态。
使用举例:
public static void ThreadProc()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("ThreadProc: {0}", i);
Thread.Sleep(1);
}
}
public static void MainThread()
{
Console.WriteLine("Main thread Start.");
Console.WriteLine("Creat a second thread: ThreadProc.");
Thread t = new Thread(new ThreadStart(ThreadProc));
Console.WriteLine("ThreadProc start.");
t.Start();
for (int i = 0; i < 4; i++)
{
Console.WriteLine("Main thread: Do some work.");
Thread.Sleep(1);
}
Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.");
t.Join();
Console.WriteLine("Main thread: ThreadProc.Join has returned. Press Enter to end program.");
Console.ReadLine();
}
ThreadStart为系统自带无参委托类型。
Thread的构造函数有如下几种:
public Thread(ThreadStart start)...
public Thread(ParameterizedThreadStart start)...
public Thread(ThreadStart start, int maxStackSize)...
public Thread(ParameterizedThreadStart start, int maxStackSize)...
ParameterizedThreadStart为带一个可控object参数的委托类型。后两种构造函数中的maxStackSize指定线程的最大堆栈大小。
Thread.Start()即启动线程
Thread.Join()即将线程加入到当前线程中使同步,或者说就是等线程结束后继续执行主线程。
Thread.Abort()是强制结束线程。
其他方法参考:Thread类
先说说进程,进程是一种正在执行的程序,操作系统使用进程来分隔正在执行的应用程序。
可以把线程池比喻成公路,一个进程只有一条公路,一条公路(线程池)上可以有多个车道。即是说一个进程只能有一个线程池,而线程池中可以有多个线程,而具体可以有多少线程呢,是受到计算机内存等限制的。
C#中可以使用 ThreadPool.GetMaxThreads 和 ThreadPool.SetMaxThreads 方法来控制最大线程数。
使用示例:
public static void ThreadProc(object stateInfo)
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("ThreadProc: {0},stateInfo: {1}", i, stateInfo);
Thread.Sleep(1);
}
}
public static void MainThreadPool()
{
ThreadPool.QueueUserWorkItem(ThreadProc, "state");
Console.WriteLine("Main thread does some work, then sleeps.");
Thread.Sleep(1000);
Console.WriteLine("Main thread exits.");
}
输出:
Main thread does some work, then sleeps.
ThreadProc: 0,stateInfo: state
ThreadProc: 1,stateInfo: state
ThreadProc: 2,stateInfo: state
ThreadProc: 3,stateInfo: state
ThreadProc: 4,stateInfo: state
Main thread exits.
ThreadPool为静态类,只能操作当前进程的线程池。
常用方法除了GetMaxThreads() 和 SetMaxThreads()外,还有上面使用示例中出现的:
QueueUserWorkItem(WaitCallback callBack, object state)
QueueUserWorkItem(WaitCallback callBack)
即将方法排入队列以便执行。 此方法在有线程池线程变得可用时执行。
参数WaitCallBack是一个内置委托(如下),需要传递参数时使用第一种方法。
delegate void WaitCallback(object? state);
这是 .NET Framework 4 中引入的基于任务的异步模式的中心组件之一,也是一部编程中优先推荐使用的。
前面已经简单说了Thread和ThreadPool了,而Task和这两个又有什么关系呢。
首先这些都是多线程异步执行,Thread是创建新线程,ThreadPool是在已有的线程上处理事情。Task是借助ThreadPool(线程池)处理。线程池即为程序进程开辟的线程池子,可以将异步任务加入到已有的线程池中的线程上执行,线程池中的线程可以复用。理解为线程池是公路,线程是路上的车道,Task为运输车(任务),一个运输任务Task在一条车道上跑过后可以有别的Task去跑。而Thread则是每次都新挖一条车道跑完再把车道毁了。
Task是更为灵活方便且被优先推荐的异步编程方式。这里说的Task包括Task(无返回值)和有返回值的 Task
先说前者,
用一个简单的例子说明使用方法:
static void MainConstruct()
{
Action<object> action = (object obj) =>
{
Console.WriteLine("obj={0}, TaskId={1}, ThreadId={2}",
obj, Task.CurrentId,
Thread.CurrentThread.ManagedThreadId);
};
Task t1 = new Task(action, "t1");
Task t2 = Task.Factory.StartNew(action, "t2");
t2.Wait();
t1.Start();
Console.WriteLine("t1 has been launched. (Main Thread={0})",Thread.CurrentThread.ManagedThreadId);
t1.Wait();
String taskData = "t3";
Task t3 = Task.Run(() => {
Console.WriteLine("obj={0}, TaskId={1}, ThreadId={2}",
taskData, Task.CurrentId,
Thread.CurrentThread.ManagedThreadId);
});
t3.Wait();
Task t4 = new Task(action, "t4");
t4.RunSynchronously();
t4.Wait();
}
输出如下:
obj=t2, TaskId=1, ThreadId=3
t1 has been launched. (Main Thread=1)
obj=t1, TaskId=2, ThreadId=3
obj=t3, TaskId=3, ThreadId=3
obj=t4, TaskId=4, ThreadId=1
Task任务的实例化可以使用构造函数以及 Task.Factory.StartNew(Action
不同的是:使用Task构造函数创建实例实际上是将任务的创建与执行分开,构造函数仅完成创建,任务的执行则需要Task实例通过Start()方法完成;而Task.Factory.StartNew()和Task.Run()在实例化任务时即让任务开始执行。
前面说的Task.Factory.StartNew()和Task.Run()在实例化时即执行。除此之外:
Task.Sart()是对已实例化但还没有开始执行,且线程状态时可以执行的任务进行启动。意义很简单,但条件较为严格。实例化的任务,如果已经开始或取消或执行结束,则不能调用Start()方法。其次Start()方法只能在任务实例化的上下文中调用。
Task.RunSynchronously()方法也是启动,但不同的是,他将实例化任务强制已同步执行。
如示例中的Task.Wait(),即在主线程(调用任务的线程)中等待任务结束。对于Task
其次,Task.ContinueWith()是设置一个在任务结束后接下来要做的另一个任务,也是已异步方式运行的。
使用Task执行异步时,取消没有像Thread.Abort()那样粗暴的方法。
需要CancellationToken,即一个消息令牌。它由CancellationTokenSource 通知。
public static void MainCancel()
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
token.Register(() => { Console.WriteLine("Canceled"); });
Task task = Task.Run(() =>
{
Console.WriteLine("Task Running...");
long v = 0;
for (int i = 0; i < 100; i++)
{
v = i * i;
Thread.Sleep(200);
Console.WriteLine("v = {0}", v);
if (token.IsCancellationRequested)
break;
}
Console.WriteLine("Task End...");
}, token);
Thread.Sleep(1000);
Console.WriteLine("Cancel Task");
source.Cancel();
}
CancellationToken可以理解为一个全局的变量,它有CancellationTokenSource创建并管理。
但为什么不直接使用自定义的全局变量来控制线程结束呢?
一方面CancellationToken是很多支持取消的内置异步方法必要参数(或者说指定取消方法),其次CancellationToken是单次的,即取消后不可重置,其他自定义变量是要具有set()方法就有可能被其他线程修改。并且当多线程操作同一数据时是有死锁的可能。
CancellationTokenSource是线程安全的,最后CancellationTokenSource提供了较为灵活且丰富的其他方法,比如示例中的token.Register()注册取消回调方法等。