一、概述程序员
最多见的并发场景包括:算法
编写快速响应的用户界面在WPF、移动应用和Windows Forms应用程序中,都须要并发执行耗时任务以保证用户界面的响应性。编程
能够处理同时出现的请求缓存
在服务器上,客户端的请求可能会并发到达,必须经过并行处理才可以保证程序的可伸缩性。若是使用ASP.NET、WCF或者WebServices,则.NET Framework会自动执行并行处理。然而,程序员仍然须要关注某些共享的状态(例如使用静态变量做为缓存)。安全
并行编程服务器
若是能够将负载划分到多个核心上,那么多核、多处理器计算机就能够提高密集计算代码的执行速度多线程
预测执行并发
在多核主机上,有时可经过预测的方式提早执行某些任务来改善程序性能。例如,LINQPad使用这种方式来提升查询的建立速度。而若是事先没法知道哪一种方法是最优的,则能够并行执行多个解决同一任务的不一样算法,最早完成的算法就是最优的。异步
这种程序同时执行代码的机制称为多线程(multithreading)。CLR和操做系统都支持多线程,它是并发的基础概念。所以,要介绍并发编程,首先就要具有线程的基础知识,特别是线程的共享状态。ide
二、线程
线程是一个能够独立执行的执行路径。每个线程都运行在一个操做系统进程中。这个进程提供了程序执行的独立环境。在单线程(single threaded)程序中,进程中只有一个线程运行,所以线程能够独立使用进程环境。而在多线程程序中,一个进程中会运行多个线程。它们共享同一个执行环境(特别是内存)。这在必定程度上说明了多线程的做用。例如,可使用一个线程在后台得到数据,同时使用另外一个线程显示所得到的数据。而这些数据就是所谓的共享状态(shared state)。
1 //建立并启动一个线程 须要首先实例化Thread 对象并调用Start方法
2 System.Threading.Thread th = new System.Threading.Thread(Print_y); 3 //启动线程
4 th.Start();
1 //线程的执行状态true || false
2 Console.WriteLine(th.IsAlive);
1 //获取当前运行的线程的名称
2 Console.WriteLine(System.Threading.Thread.CurrentThread.Name);
1 //建立并启动一个线程 须要首先实例化Thread 对象并调用Start方法
2 System.Threading.Thread th = new System.Threading.Thread(Print_y); 3 //启动线程
4 th.Start(); 5 //等待线程结束,能够指定超时时间
6 th.Join(1000); 7
8 //暂停2秒钟
9 System.Threading.Thread.Sleep(2000);
1 ////可使用ThreadState属性测试线程的阻塞状态
2 bool b = (th.ThreadState & System.Threading.ThreadState.WaitSleepJoin)!=0;
I/O密集和计算密集
若是一个操做的绝大部分时间都在等待事件的发生,则称为I/O密集,例以下载网页或者调用Console.ReadLine。(I/O密集操做通常都会涉及输入或者输出,可是这并不是硬性要求。例如Thread.Sleep也是一种I/O密集的操做)。而相反的,若是操做的大部分时间都用于执行大量的CPU操做,则称为计算密集。
阻塞与自旋
I/O密集操做主要表现为如下两种形式:要么在当前线程同步进行等待,直至操做完成(例如Console.ReadLine、Thread.Sleep以及Thread.Join);要么异步进行操做,在操做完成的时候或者以后某个时刻触发回调函数(以后将详细介绍)。同步的I/O密集操做的大部分时间都花费在阻塞线程上,可是也可能在一个按期循环中自旋:
while(DataTime.Now<nextStartTime) { Thread.Sleep(1000); }
本地状态与共享状态
CLR为每个线程分配了独立的内存栈,从而保证了局部变量的隔离。下面的示例定义了一个拥有局部变量的方法,并同时在主线程和新建立的线程中调用该方法:
static void Main(string[] args) { //开启一个新的线程
new System.Threading.Thread(Go).Start(); //主线程
Go(); Console.ReadKey(); } static void Go() { for (int count = 0; count < 5; count++) { Console.WriteLine("? "); } }
因为每个线程的内存栈上都会有一个独立的cycles变量的副本,所以咱们能够预测,程序的输出将是10个问号。
若是不一样的线程拥有同一个对象的引用,则这些线程之间就共享了数据:
因为两个线程均在同一个ThreadTest实例上调用了Go()方法,所以它们共享_done字段。所以,“Done”只会打印一次,而非两次。
锁与线程安全
咱们能够在读写共享字段时首先得到一个排它锁来修正以前示例的问题。使用C#的lock语句就能够实现这个目标:
class Program { static bool _done; static readonly object locker = new object(); static void Main(string[] args) { new System.Threading.Thread(()=>Go("Down")).Start(); Go("ss"); Console.ReadKey(); } public static void Go(string str) { lock (locker) { if (!_done) { Console.WriteLine(str); _done = true; } } } }
向线程传递数据
有时咱们须要给线程的启动方法传递参数。最简单的方案是使用Lambda表达式,并在其中使用指定的参数调用相应的方法。
Lambda表达式是在C#3.0以后引入的,你可能会遇到一些代码使用了较老的技术,即向Thread的Start方法传递一个参数:
前台线程与后台线程
通常状况下,显式建立的线程称为前台线程(Foreground thread)。只要有一个前台线程还在运行,应用程序就仍然保持运行状态。然后台线程(background thread)则否则。当全部前台线程结束时,应用程序就会中止,且全部运行的后台线程也会随之终止。
线程的前台/后台状态和线程的优先级(执行时间的分配)无关。可使用线程的IsBackground属性来查询或修改线程的先后台状态:
信号发送
有时一个线程须要等待来自其余线程的通知,即所谓的信号发送(signaling)。最简单的信号发送结构是ManualResetEvent。调用ManualResetEvent的WaitOne方法能够阻塞当前线程,直到其余线程调用了Set“打开”了信号。如下的示例启动了一个线程,并等待ManualResetEvent。它会阻塞两秒钟,直至主线程发送信号为止:
//是否将初始值设置为终止
var signal = new ManualResetEvent(false); new Thread(() => { Console.WriteLine("Waiting for signal...."); signal.WaitOne(); signal.Dispose(); Console.WriteLine("Got signal!"); }).Start(); Thread.Sleep(2000); signal.Set();
在Set调用后,信号发送结构仍然会保持“打开”状态,能够调用Reset方法再次将其“关闭”。
跨线程操做
//· 在WPF中,调用元素上的Dispatcher对象的BeginInvoke或Invoke方法。 //· 在UWP应用中,能够调用Dispatcher对象的RunAsync或Invoke方法。 //· 在Windows Forms应用中:调用控件的BeginInvoke或Invoke方法。 private void button3_Click(object sender, EventArgs e) { Thread th = new Thread(Print); th.IsBackground = true; th.Start(); } public void Print() { if (textBox1.InvokeRequired)//是否对文本框进行跨线程访问
{ //invoke:去找建立TextBox的线程
this.textBox1.Invoke(new Action<TextBox, string>(showTxt), this.textBox1, "123"); } else { textBox1.Text = "123"; } } public void showTxt(TextBox tet, string msg) { tet.Text = msg; }
线程池
在线程池上运行代码的最简单的方式是调用Task.Run(咱们将在下一节深刻介绍这个方法):
//在线程池上运行代码的最简单的方法是 Task.Run
Task.Run(() => { Console.WriteLine("hello world"); }); //.net4.0 以前没有Task类 所以能够调用下面的方法
ThreadPool.QueueUserWorkItem(n => Console.WriteLine(n));