谈多线程谈到现在
![](/icons/1276dou.gif)
我们要明确多线程
![](/icons/1276de.gif)
![](/icons/1276yi.gif)
个好处是可以进行并行
![](/icons/1276de.gif)
运算(充分利用多核处理器
![](/icons/1276dou.gif)
对于桌面应用
![](/icons/1276chengxu.gif)
来说就更重要
![](/icons/1276yi.gif)
点了
![](/icons/1276dou.gif)
没有WEB服务器
![](/icons/1276dou.gif)
利用多核只能靠自己)
![](/icons/1276dou.gif)
还有
![](/icons/1276yi.gif)
个好处就是异步操作
![](/icons/1276dou.gif)
就是我们可以让某个长时间
![](/icons/1276de.gif)
操作独立运行
![](/icons/1276dou.gif)
不妨碍主线程继续进行
![](/icons/1276yi.gif)
些计算
![](/icons/1276dou.gif)
然后异步
![](/icons/1276de.gif)
去返回结果(也可以不返回)
![](/icons/1276dou2.gif)
前者能提高性能是
![](/icons/1276yinwei.gif)
能利用到多核
![](/icons/1276dou.gif)
而后者能提高性能是
![](/icons/1276yinwei.gif)
能让CPU不在等待中白白浪费
![](/icons/1276dou.gif)
其实异步从广义上来说也可以理解为某种并行
![](/icons/1276de.gif)
运算
![](/icons/1276dou2.gif)
在的前
![](/icons/1276de.gif)
这么多例子中
![](/icons/1276dou.gif)
我们大多采用手工方式来新开线程
![](/icons/1276dou.gif)
的前也说过了
![](/icons/1276dou.gif)
在大并发
![](/icons/1276de.gif)
环境中随便开始和结束线程
![](/icons/1276de.gif)
代价太大
![](/icons/1276dou.gif)
需要利用线程池
![](/icons/1276dou.gif)
使用线程池
![](/icons/1276de.gif)
话又觉得少了
![](/icons/1276yi.gif)
些控制
![](/icons/1276dou2.gif)
现在让我们来整理总结
![](/icons/1276yi.gif)
下大概会有哪几种常见
![](/icons/1276de.gif)
异步编程应用模式:
1) 新开
![](/icons/1276yi.gif)
个A线程执行
![](/icons/1276yi.gif)
个任务
![](/icons/1276dou.gif)
然后主线程执行另
![](/icons/1276yi.gif)
个任务后等待线程返回结果后继续
2) 新开
![](/icons/1276yi.gif)
个A线程执行
![](/icons/1276yi.gif)
个任务
![](/icons/1276dou.gif)
然后主线程不断轮询A线程是否执行完毕
![](/icons/1276dou.gif)
如果没有
![](/icons/1276de.gif)
话可以选择等待或是再进行
![](/icons/1276yi.gif)
些操作
3) 新开
![](/icons/1276yi.gif)
个A线程执行
![](/icons/1276yi.gif)
个任务
![](/icons/1276dou.gif)
执行完毕的后立即执行
![](/icons/1276yi.gif)
个回调思路方法去更新
![](/icons/1276yi.gif)
些状态变量
![](/icons/1276dou.gif)
主线程和A线程不
![](/icons/1276yi.gif)
定有直接交互
4) 新开
![](/icons/1276yi.gif)
个A线程执行
![](/icons/1276yi.gif)
个任务
![](/icons/1276dou.gif)
执行完毕的后啥都不做
(补充
![](/icons/1276yi.gif)
句
![](/icons/1276dou.gif)
异步编程不
![](/icons/1276yi.gif)
定是依赖于线程
![](/icons/1276de.gif)
![](/icons/1276dou.gif)
从广义上来说
![](/icons/1276dou.gif)
使用队列异步处理数据也可以算是
![](/icons/1276yi.gif)
种异步编程模式)
对于这任何
![](/icons/1276yi.gif)
种
![](/icons/1276dou.gif)
我们要使用线程池来编写应用
![](/icons/1276de.gif)
话都是比较麻烦
![](/icons/1276de.gif)
![](/icons/1276dou.gif)
比如如下
![](/icons/1276de.gif)
代码实现了1)这种应用:
AsyncObj
{
public EventWaitHandle AsyncWaitHandle {
get;
![](/icons/1276set.gif)
; }
public object Result {
get;
![](/icons/1276set.gif)
; }
public AsyncObj
![](/icons/1276kh.gif)
{
AsyncWaitHandle =
AutoRe
Event(
false);
}
}
AsyncObj ao =
AsyncObj![](/icons/1276kh.gif)
;
ThreadPool.QueueUserWorkItem(state =>
{
AsyncObj obj = state
as AsyncObj;
Console.WriteLine(
"asyc operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Thread.Sleep(2000);
Console.WriteLine(
"asyc operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
obj.Result = 100;
obj.AsyncWaitHandle.Set
![](/icons/1276kh.gif)
;
}, ao);
Console.WriteLine(
"
operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Thread.Sleep(1000);
Console.WriteLine(
"
operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
ao.AsyncWaitHandle.WaitOne
![](/icons/1276kh.gif)
;
Console.WriteLine(
"get syc operation result : " + ao.Result.
![](/icons/1276ToString.gif)
![](/icons/1276kh.gif)
+
" @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
结果如下:
对于2)-4)等情况又是另外
![](/icons/1276yi.gif)
套了
![](/icons/1276dou.gif)
这样我们
![](/icons/1276de.gif)
代码可能会变得乱 7 8糟
![](/icons/1276dou.gif)
在.NET中我们
![](/icons/1276de.gif)
委托以及很多IO操作相关
![](/icons/1276de.gif)
类库都支持
![](/icons/1276yi.gif)
种叫做异步编程模型APM
![](/icons/1276de.gif)
编程模型
![](/icons/1276dou2.gif)
不仅仅方便了我们进行多线程应用
![](/icons/1276dou.gif)
而且我们如果自己要设计类库
![](/icons/1276de.gif)
话也可以遵从这个模式(基于APM
![](/icons/1276de.gif)
接口实现我们自己
![](/icons/1276de.gif)
类库)
![](/icons/1276dou2.gif)
.NET提供了基于IAsyncResult
![](/icons/1276de.gif)
异步编程模型和基于事件
![](/icons/1276de.gif)
异步编程模型
![](/icons/1276dou.gif)
这节我们来看看基于IAsyncResult也就是BeginInvoke和EndInvoke(对于非同用
![](/icons/1276de.gif)
操作来说就是BeginXXX和EndXXX)
![](/icons/1276de.gif)
编程模型
![](/icons/1276de.gif)
各种使用思路方法
![](/icons/1276dou.gif)
可以说这么多种使用思路方法可以满足我们大部分
![](/icons/1276de.gif)
要求
![](/icons/1276dou2.gif)
首先来定义
![](/icons/1276yi.gif)
个异步操作:
AsyncOperation(
x,
y)
{
Console.WriteLine(
"asyc operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Thread.Sleep(2000);
a, b;
ThreadPool.GetAvailableThreads(
out a,
out b);
Console.WriteLine(
![](/icons/1276string.gif)
.Format(
"({0}/{1}) #{2}", a, b,
Thread.CurrentThread.ManagedThreadId));
Console.WriteLine(
"asyc operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
x + y;
}
我们需要开两个线程同时计算两个异步操作
![](/icons/1276dou.gif)
然后主线程等待两个线程执行完毕后获取结果并且输出它们
![](/icons/1276de.gif)
和
![](/icons/1276dou.gif)
难以想象代码是多么简单:
var func =
Func<
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
>(AsyncOperation);
var result1 = func.BeginInvoke(100, 200,
null,
null);
var result2 = func.BeginInvoke(300, 400,
null,
null);
Console.WriteLine(
"
operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Thread.Sleep(1000);
Console.WriteLine(
"
operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
result = func.EndInvoke(result1) + func.EndInvoke(result2);
Console.WriteLine(
"get syc operation result : " + result +
" @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
主线程
![](/icons/1276de.gif)
计算需要1秒
![](/icons/1276dou.gif)
两个异步线程都需要2秒
![](/icons/1276dou.gif)
整个
![](/icons/1276chengxu.gif)
理论上需要2秒执行完毕
![](/icons/1276dou.gif)
看看结果如何:
当然
![](/icons/1276dou.gif)
在的前我们限制了线程池
![](/icons/1276de.gif)
线程数为2-4:
ThreadPool.SetMinThreads(2, 2);
ThreadPool.SetMaxThreads(4, 4);
从结果中可以看出
![](/icons/1276dou.gif)
使用委托来异步
![](/icons/1276diaoyong.gif)
思路方法基于线程池
![](/icons/1276dou.gif)
![](/icons/1276diaoyong.gif)
EndInvoke
![](/icons/1276de.gif)
时候阻塞了主线程
![](/icons/1276dou.gif)
得到结果后主线程继续
![](/icons/1276dou2.gif)
在代码中没看到Thread没看到ThreadPool没看到信号量
![](/icons/1276dou.gif)
我们却完成了
![](/icons/1276yi.gif)
个异步操作
![](/icons/1276dou.gif)
实现了
![](/icons/1276yi.gif)
开始说
![](/icons/1276de.gif)
1)场景
![](/icons/1276dou2.gif)
现在再来看看第 2种使用方式:
var func =
Func<
![](/icons/1276string.gif)
,
![](/icons/1276int.gif)
,
![](/icons/1276string.gif)
>(AsyncOperation2);
var result1 = func.BeginInvoke(
"hello ", 2000,
null,
null);
var result2 = func.BeginInvoke(
"world ", 3000,
null,
null);
Console.WriteLine(
"
operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Thread.Sleep(1000);
Console.WriteLine(
"
operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
WaitHandle.WaitAny(
WaitHandle![](/icons/1276zhk2.gif)
{ result1.AsyncWaitHandle, result2.AsyncWaitHandle });
r1 = result1.IsCompleted ? func.EndInvoke(result1) :
![](/icons/1276string.gif)
.Empty;
r2 = result2.IsCompleted ? func.EndInvoke(result2) :
![](/icons/1276string.gif)
.Empty;
(
![](/icons/1276string.gif)
.IsNullOrEmpty(r1))
{
Console.WriteLine(
"get syc operation result : " + r2 +
" @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
func.EndInvoke(result1);
}
(
![](/icons/1276string.gif)
.IsNullOrEmpty(r2))
{
Console.WriteLine(
"get syc operation result : " + r1 +
" @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
func.EndInvoke(result2);
}
BeginInvoke返回
![](/icons/1276de.gif)
是
![](/icons/1276yi.gif)
个IAsyncResult
![](/icons/1276dou.gif)
通过其AsyncWaitHandle 属性来获取WaitHandle
![](/icons/1276dou2.gif)
异步
![](/icons/1276diaoyong.gif)
完成时会发出信号量
![](/icons/1276dou2.gif)
这样我们就可以更灵活
![](/icons/1276yi.gif)
些了
![](/icons/1276dou.gif)
可以在需要
![](/icons/1276de.gif)
时候去WaitOne
![](/icons/1276kh.gif)
(可以设置超时时间)
![](/icons/1276dou.gif)
也可以WaitAny
![](/icons/1276kh.gif)
或是WaitAll
![](/icons/1276kh.gif)
![](/icons/1276dou.gif)
上例我们实现
![](/icons/1276de.gif)
效果是开了2个线程
![](/icons/1276yi.gif)
个3秒
![](/icons/1276dou.gif)
![](/icons/1276yi.gif)
个2秒
![](/icons/1276dou.gif)
只要有任何
![](/icons/1276yi.gif)
个完成就获取其结果
![](/icons/1276dou.gif)
主线程任务完成的后再去EndInvoke没完成
![](/icons/1276de.gif)
那个来释放资源(比如有两个排序算法
![](/icons/1276dou.gif)
它们哪个快取决于数据源
![](/icons/1276dou.gif)
我们
![](/icons/1276yi.gif)
起执行并且只要有
![](/icons/1276yi.gif)
个得到结果就继续)
![](/icons/1276dou2.gif)
在这里我们
![](/icons/1276de.gif)
工作思路方法AsyncOperation2
![](/icons/1276de.gif)
定义如下:
AsyncOperation2(
s,
time)
{
Console.WriteLine(
"asyc operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss:fff"));
Thread.Sleep(time);
a, b;
ThreadPool.GetAvailableThreads(
out a,
out b);
Console.WriteLine(
![](/icons/1276string.gif)
.Format(
"({0}/{1}) #{2}", a, b,
Thread.CurrentThread.ManagedThreadId));
Console.WriteLine(
"asyc operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss:fff"));
s.ToUpper
![](/icons/1276kh.gif)
;
}
这段
![](/icons/1276chengxu.gif)
运行结果如下:
可以看到
![](/icons/1276dou.gif)
在2秒
![](/icons/1276de.gif)
那个线程结束后
![](/icons/1276dou.gif)
主线程就继续了
![](/icons/1276dou.gif)
然后再是3秒
![](/icons/1276de.gif)
那个线程结束
![](/icons/1276dou2.gif)
再来看看第 3种
![](/icons/1276dou.gif)
也就是使用轮询
![](/icons/1276de.gif)
方式来等待结果:
var func =
Func<
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
>(AsyncOperation);
var result = func.BeginInvoke(100, 200,
null,
null);
Console.WriteLine(
"
operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Thread.Sleep(1000);
Console.WriteLine(
"
operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
while (!result.IsCompleted)
{
Console.WriteLine(
"
thread wait again");
Thread.Sleep(500);
}
r = func.EndInvoke(result);
Console.WriteLine(
"get syc operation result : " + r +
" @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
![](/icons/1276chengxu.gif)
![](/icons/1276de.gif)
输出结果如下
![](/icons/1276dou.gif)
这对应我们
![](/icons/1276yi.gif)
开始提到
![](/icons/1276de.gif)
第 2种场景
![](/icons/1276dou.gif)
在等待
![](/icons/1276de.gif)
时候我们
![](/icons/1276de.gif)
主线程还可以做
![](/icons/1276yi.gif)
些(不依赖于返回结果
![](/icons/1276de.gif)
)计算呢:
再来看看第 4种
![](/icons/1276dou.gif)
采用回调
![](/icons/1276de.gif)
方式来获取结果
![](/icons/1276dou.gif)
线程在结束后自动
![](/icons/1276diaoyong.gif)
回调思路方法
![](/icons/1276dou.gif)
我们可以在回调思路方法中进行EndInvoke:
var func =
Func<
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
>(AsyncOperation);
var result = func.BeginInvoke(100, 200, CallbackMethod, func);
Console.WriteLine(
"
operation started @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Thread.Sleep(1000);
Console.WriteLine(
"
operation completed @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
Console.ReadLine
![](/icons/1276kh.gif)
;
BeginInvoke
![](/icons/1276de.gif)
第 3个参数是回调思路方法
![](/icons/1276dou.gif)
第 4个参数是传给工作思路方法
![](/icons/1276de.gif)
状态变量
![](/icons/1276dou.gif)
这里我们把工作思路方法
![](/icons/1276de.gif)
委托传给它
![](/icons/1276dou.gif)
这样我们可以在回调思路方法中获取到这个委托:
void CallbackMethod(
IAsyncResult ar)
{
Console.WriteLine(
![](/icons/1276string.gif)
.Format(
"CallbackMethod runs _disibledevent=>Thread.CurrentThread.ManagedThreadId));
var caller = (
Func<
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
,
![](/icons/1276int.gif)
>)ar.AsyncState;
r = caller.EndInvoke(ar);
Console.WriteLine(
"get syc operation result : " + r +
" @ " +
DateTime.Now.
![](/icons/1276ToString.gif)
(
"mm:ss"));
}
![](/icons/1276chengxu.gif)
![](/icons/1276de.gif)
输出结果如下:
可以看到
![](/icons/1276dou.gif)
主线程并没有
![](/icons/1276yinwei.gif)
工作线程而阻塞
![](/icons/1276dou.gif)
它没有等待它
![](/icons/1276de.gif)
结果
![](/icons/1276dou.gif)
异步思路方法结束后自动
![](/icons/1276diaoyong.gif)
回调思路方法(运行于新线程)
![](/icons/1276dou.gif)
在回调思路方法中我们把状态变量进行类型转换后得到思路方法委托
![](/icons/1276dou.gif)
然后通过这个委托来
![](/icons/1276diaoyong.gif)
EndInvoke获得结果
![](/icons/1276dou2.gif)
这里符合我们第3)种应用
![](/icons/1276dou.gif)
这种情况下主线程不
![](/icons/1276yi.gif)
定需要和异步思路方法进行直接
![](/icons/1276de.gif)
交互(也就无需等待)
![](/icons/1276dou.gif)
当然主线程也完全可以再结合使用轮询或等待信号量等待异步线程完成后从共享变量(需要回调思路方法把结果写入共享变量)来获取结果
![](/icons/1276dou2.gif)
至于
![](/icons/1276yi.gif)
开始说
![](/icons/1276de.gif)
第4)种应用需要注意
![](/icons/1276dou.gif)
我们完全可以直接采用线程池来做
![](/icons/1276dou.gif)
如果采用异步编程模型
![](/icons/1276de.gif)
话
![](/icons/1276dou.gif)
即使不需要得到结果也别忘记
![](/icons/1276diaoyong.gif)
EndInvoke来释放资源
![](/icons/1276dou.gif)
这是
![](/icons/1276yi.gif)
个好习惯
![](/icons/1276dou.gif)
![](/icons/1276yinwei.gif)
.NET中很多涉及到IO和网络操作
![](/icons/1276de.gif)
类库都采用了APM方式
![](/icons/1276dou.gif)
对于这些应用如果我们不
![](/icons/1276diaoyong.gif)
EndInvoke来释放非托管资源
![](/icons/1276de.gif)
话
![](/icons/1276dou.gif)
GC恐怕无能为力
![](/icons/1276de.gif)
![](/icons/1276dou2.gif)
下节继续讨论基于事件
![](/icons/1276de.gif)
异步编程模式
![](/icons/1276dou2.gif)
作者:lovecindywang
本文版权归作者和博客园共有
![](/icons/1276dou.gif)
欢迎转载
![](/icons/1276dou.gif)
但未经作者同意必须保留此段声明
![](/icons/1276dou.gif)
且在文章页面明显位置给出原文连接
![](/icons/1276dou.gif)
否则保留追究法律责任
![](/icons/1276de.gif)
权利