C#程序设计.唐大仕.11.多线程与异步编程

多线程与异步编程

1. 线程及其创建

  • 进程 Process
  • 线程 Thread
    • 线程中的指令:一个方法(委托)
    • 线程中的数据:相关的对象
    • 具体调度由操作系统和 .net 环境负责
  • .net
1
using System.Threading.Thread;

属性

Property 描述
CurrentPrincipal 获取或者设定线程的当前安全性
CurrentThread 获得对当前正在运行的线程的一个引用(static属性)
IsAlive 如果线程已经被启动并且尚在生命周期内,则返回 True
IsBackground 如果目标线程是在后台执行的,则为此属性赋值为 True
Name 获取或者设定这个线程的名字
Priority 获取或者设定这个线程的优先级
ThreadState 获得线程的当前状态

方法

Method 描述
Abort 撤消这个线程
Interrupt 如果线程处于 WaitSleepJoin 状态,则中断它
Join 等待一个线程的结束
Resume 将被挂起的线程重新开始
Sleep 让线程休眠一定时间
Start 启动一个线程
Suspend 挂起一个线程

创建线程

1
2
3
Thread thread = new Thread(new ThreadStart(obj.fun()));
// 启动线程
thread.Start();

线程的停止

  • 线程函数会一直执行下去,直至它结束
  • Abort() 终止
  • Suspend() 挂起
    • Resume() 恢复
  • Sleep(毫秒数)

线程的状态

  • ThreadState 枚举类
成员 描述
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
lock( /* 对象或者表达式 */ ){
// statement
}
  • lock 的内部实现
1
2
3
4
5
6
System.Threading.Monitor.Enter(/* 对象或表达式 */);
try {
// statement
} 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 类

  • 使用 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

  • PLinq
    • AsParallel() 即可
1
2
3
var query2 = (from n in dic.Values.AsParallel()
where n.Age > 20 && n.Age < 25
select n).ToList();

7. 异步编程

  • 异步 asynchronize
  • 主要解决的事情是
    • 等待一些耗时的任务(特别是文件、网络操作)而不阻塞当前任务
    • 异步编程提高响应能力(特别是 UI)
  • 开始一个任务后,让任务在另一个线程中执行,本线程可以继续执行别的事情,然后等待那个任务执行完毕

传统方法

1
2
3
4
5
PrintDelegate printDelegate = Print;
IAsyncResult result = printDelegate.BeginInvoke("Hello World.", null, null);
Console.WriteLine("主线程继续执行...");
// 当使用BeginInvoke异步调用方法时,如果方法未执行完,EndInvoke方法就会一直阻塞,直到被调用的方法执行完毕
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表示要执行任务
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);
});

WinForm

  • 当异步执行完成后,使用界面线程来执行回调,所以写起来更简洁
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; // 等待异步执行完毕