0%
多线程与异步编程
1. 线程及其创建
- 进程 Process
- 线程 Thread
- 线程中的指令:一个方法(委托)
- 线程中的数据:相关的对象
- 具体调度由操作系统和 .net 环境负责
- .net
1
| using System.Threading.Thread;
|
属性
CurrentPrincipal |
获取或者设定线程的当前安全性 |
CurrentThread |
获得对当前正在运行的线程的一个引用(static属性) |
IsAlive |
如果线程已经被启动并且尚在生命周期内,则返回
True |
IsBackground |
如果目标线程是在后台执行的,则为此属性赋值为
True |
Name |
获取或者设定这个线程的名字 |
Priority |
获取或者设定这个线程的优先级 |
ThreadState |
获得线程的当前状态 |
方法
Abort |
撤消这个线程 |
Interrupt |
如果线程处于 WaitSleepJoin
状态,则中断它 |
Join |
等待一个线程的结束 |
Resume |
将被挂起的线程重新开始 |
Sleep |
让线程休眠一定时间 |
Start |
启动一个线程 |
Suspend |
挂起一个线程 |
创建线程
1 2 3
| Thread thread = new Thread(new ThreadStart(obj.fun()));
thread.Start();
|
线程的停止
- 线程函数会一直执行下去,直至它结束
- Abort() 终止
- Suspend() 挂起
- Sleep(毫秒数)
线程的状态
Aborted |
线程已经被中断并且被撤销 |
AbortRequested |
线程正在被请求中断 |
Background |
线程充当后台线程的角色,并且正在执行 |
Running |
线程正在运行 |
Stopped |
线程停止运行(这个状态只限于内部使用) |
StopRequested |
线程正在被要求停止(这个状态只限于内部使用) |
Suspended |
线程已经被挂起 |
SuspendRequested |
线程已经被要求挂起 |
Unstarted |
线程还没有被启动 |
WaitSleepJoin |
线程在一次 Wait()、Sleep() 以及 Join()
调用中被锁定 |
线程优先级
- ThreadPriority 枚举类
- Highest、AboveNormal、Normal、BelowNormal、Lowest
- 正常为 Normal
代码示例
2. 线程同步控制
Join() 方法
- 单独的执行线程合并成一个线程
- 等待该线程执行结束,再进一步往下执行
Lock 语句与 Monitor 类
1 2 3 4 5 6
| System.Threading.Monitor.Enter(); try { } finally { System.Threading.Monitor.Exit(); }
|
用于同步控制的类
AutoResetEvent |
等待句柄,用于通知一个或多个等待线程发生了一个事件 AutoResetEvent在等待线程被释放后自动将状态更改为已发出信号 |
Interlocked |
为多个线程共享的变量提供原子操作 |
ManualResetEvent |
等待句柄,用于通知一个或多个等待线程发生了一个事件 手动重置事件的状态将保持为已发出信号,直至
Reset
方法将其设置为未发出信号状态 同样,该状态将保持为未发出信号,直至
Set
方法将其设置为已发出信号状态 当对象的状态为已发出信号时,任意数量的等待线程(即通过调用一个等待函数开始对指定事件对象执行等待操作的线程)都可以被释放 |
Monitor |
提供同步访问对象的机制 |
Mutex |
等待句柄,可用于进程间同步 |
ReaderWriterLock |
定义用于实现单个写入者和多个读取者的锁定 |
Timer |
提供按指定间隔运行任务的机制 |
WaitHandle |
封装操作系统特有的、等待对共享资源进行独占访问的对象 |
3. 线程池及其他线程类
Threadpool
- Threadpool.QueueUserWorkItem()等方法来提交相应的任务
- QueueUserWorkItem(WaitCallback, object)
- QueueUserWorkItem(WaitCallback) 其中public delegate void
WaitCallback( object state );
Timer
- System.Threading.Timer
- 构造方法
1 2 3 4 5 6 7 8
| public Timer( TimerCallback callback, //执行的任务 object state, // 数据 int dueTime, // 启动前的延时 int period // 任务之间的间隔 );
public delegate void TimerCallback(object state);
|
4. 线程在集合中使用
- IsSynchoronized 属性用于判断是否为同步版本
- SyncRoot 属性提供了集合自己的同步版本
- Array,ArrayList,SortedList,Hashtable 等,都可以使用
Synchronized() 方法获取一个线程安全的包装对象
- 代码示例
5. 线程在 Window 界面中使用
- BeginInvoke
- 界面的主线程
- 对界面的更新只能使用主线程
- 其他线程则可以这样
1 2 3 4 5 6
| if(this.InvokeRequired){ this.BeginInvoke(new AddMsg(this.AddMsgFun), new object[]{msg}); } else { this.AddMsgFun( msg); }
|
- 使用 BackgroundWorker 组件
- DoWork 事件
- RunWorkerAsync 方法
6. 并行编程
并行任务库 TPL
- 并行任务库(TPL,Task Parallel Library)
- 最重要的是 Task 类、Parallel 类
- Task 类,是利用线程池来进行任务的执行
- 比直接用 ThreadPool 更优化,而且编程更方便
- Parallel 类,是并行执行任务类的实用类
Task 类
- 使用 Task.Run 方法来得到 Task 的实例
1 2 3 4 5
| Task<double> task = Task.Run( ()=>SomeFun() ); double result = task.Result;
Task.WaitAll( task 数组); task.ContinueWith(另一个task);
|
Task 中的异常
1 2 3 4 5 6 7 8
| try{ Task.WaitAll(task1,task2,task3); } catch (AggregateException ex) { foreach(Exception inner in ex.InnerExceptions) { Console.WriteLine("Exceptiontype{0} from{1}",inner.GetType(),inner.Source); } }
|
Parallel 类
1 2 3 4
| Parallel.Invoke( Action[] actions);
Parallel.For(0, 100, i => {} ); Parallel.ForEach( list, item => { } );
|
并行 Linq
1 2 3
| var query2 = (from n in dic.Values.AsParallel() where n.Age > 20 && n.Age < 25 select n).ToList();
|
7. 异步编程
- 异步 asynchronize
- 主要解决的事情是
- 等待一些耗时的任务(特别是文件、网络操作)而不阻塞当前任务
- 异步编程提高响应能力(特别是 UI)
- 开始一个任务后,让任务在另一个线程中执行,本线程可以继续执行别的事情,然后等待那个任务执行完毕
传统方法
- 使用委托的 BeginInvoke 及 EndInvoke
1 2 3 4 5
| PrintDelegate printDelegate = Print; IAsyncResult result = printDelegate.BeginInvoke("Hello World.", null, null); Console.WriteLine("主线程继续执行...");
int n = printDelegate.EndInvoke(result);
|
- 使用回调
- 代码示例
- 回调函数可能在主线程执行,也有可能为了节省开销在子线程执行
C# 5.0 新方法
- 新增 await 及 async 两个关键词
- await 表示等待任务的执行
- async 修饰一个方法,表示其中有 await 语句
1 2 3 4 5 6 7 8 9 10 11 12 13
| Task<double>FacAsync(int n) { return Task<double>.Run( ()=>{ double s = 1; for(int i=1; i<n; i++){ s = s*i; } return s; }); }
async void Test() { double result = await FacAsync(10); Console.WriteLine( result); }
|
1 2 3
| double result = await FacAsync(10);
Console.WriteLine( result);
|
- 它解决了传统方法中 “异步任务与回调方法分开写”
的问题,相当于如下代码
1 2 3 4 5 6
| System.Runtime.CompilerServices.TaskAwaiter<double>awaiter = FacAsync(10).GetAwaiter(); awaiter.OnCompleted(()=>{ doubleresult=awaiter.GetResult(); Console.WriteLine( result); });
|
- 当异步执行完成后,使用界面线程来执行回调,所以写起来更简洁
1 2 3 4
| asyncprivate void button1_Click(object sender, EventArgse) { string content = awaitAccessTheWebAsync(url); this.textBox2.Text = content; }
|
异步的流
- 与上面的 HttpClient 相似,Stream等类也提供了异步方法
1
| await myStream.WriteAsync();
|
- 这比传统的 BeginWrite() + 回调函数 + EndWrite() 要方便很多
- 也可以这样
1 2 3
| Task task= myStream.WriteAsync(); DoIndependentWork(); await task;
|