首先介绍下Classic Async Pattern:
其实Classic Async Pattern指的就是我们常见的BeginXXX和EndXXX
IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来实现原同步方法的异步调用
让我们再来回顾下.net中的几个的BeginXXX 和EndXXX
Stream中的BeginRead,EndRead,BeginWrite,EndWriteSocket中的BeginReceive,EndReceiveHttpWebRequest的BeginGetRequestStream和EndGetRequestStream.... |
---|
再来介绍下Event-based Async Pattern
Event-based Async Pattern 值的是类似于 xxxxxxxAsync() 和 类似于event xxxxxCompleteHander
通过一个方法和一个完成事件来处理异步操作
.net中的例子:
WebClient.DownloadStringAsync(string uri)和 event DownloadStringCompleteEventHandler |
---|
其实Classic Async Pattern和Event-based Async Pattern都是一种异步的设计思路,我们也可以根据这一系列的
思路去实现自己的异步方法
7 异步线程的发展趋势以及.net4.5中异步的简化
微软貌似现在把精力放在win8或WinPhone的metro上,而且记得在win 8开发者培训的会议上,着重阐述了微软对于异步的支持将越来越强,而且为了快
速响应诸如移动设备的应用程序,微软也在争取为每个方法都实现一个异步版本…..可见异步的重要性,相信异步的发展趋势是个不错的
上升曲线,还没反应过来.net4.5的异步新特性便诞生了。首先经历过异步摧残的我们,都会有这样一个感受,往往回调方法和普通方法
会搞错,在复杂的项目面前,有时候简直无法维护,到处都是回调函数,眼花缭乱 所以微软为了简化异步的实现过程,甚至大刀阔斧将
回调函数做成看起来像同步方法,虽然觉得很诡异,还是让我们初步了解下这种异步的新特性
先看代码
/// <summary>
/// .net 4.5 中 async 和 await 全新的关键字 一起实现异步的简化
/// </summary>
void async ShowUriContent(string uri)
{
using (FileStream fs = File.OpenRead("你的文件地址"))
{
using (FileStream fs2 = new FileStream())
{
byte[] buffer = new byte[4096];
//FileStream的ReadAsync方法也是net4.5版本出现的,它返回一个Task<int>对象
//而且作用于await后的异步代码会等待阻塞直到异步方法完成后返回
int fileBytesLength = await fs.ReadAsync(buffer,0,buffer.Length).ConfigureAwait(false);
while(fileBytesLength>0)
{
//FileStream的WriteAsync方法也是net4.5版本出现的
await fs2.WriteAsync(buffer,0,buffer.Length).ConfigureAwait(false);
}
}
}
}
相信看完代码后大家有耳目一新的感觉,不错,原本异步调用的回调函数不见了,取而代之的是await和方法声明上的async关键字,新特性允许
我们实现这俩个关键字后便能在方法中实现“同步方式”的异步方法,其实这解决了一些棘手的问题,诸如原本需要在回调事件里才能释放的文件句
柄在这里和同步方法一样,使用using便搞定了,还有截获异常等等,都不用像之前那样痛苦了,这里还有一些东东需要关注下,大家先不用去深
究ConfigureAwait这个方法,由于ReadAsync和 WriteAsync方法是.net 4.5新加的属于返回Task<int>类型的方法所以使用ConfigureAwait
方法能够将数值取到,关于Task泛型类我会在今后的章节中详细阐述
8 本章示例
自定义一个简单的线程池
static void Main(string[] args)
{
ThreadStart[] startArray =
{
new ThreadStart(()=>{
Console.WriteLine("第一个任务");
}),
new ThreadStart(()=>{Console.WriteLine("第二个任务");}),
new ThreadStart(()=>{Console.WriteLine("第三个任务");}),
new ThreadStart(()=>{Console.WriteLine("第四个任务");}),
};
MyThreadPool.SetMaxWorkThreadCount(2);
MyThreadPool.MyQueueUserWorkItem(startArray);
Console.ReadKey();
}
/// <summary>
/// 自定义一个简单的线程池,该线程池实现了默认开启线程数
/// 当最大线程数全部在繁忙时,循环等待,只到至少一个线程空闲为止
/// 本示例使用BackgroundWorker模拟后台线程,任务将自动进入队列和离开
/// 队列
/// </summary>
sealed class MyThreadPool
{
//线程锁对象
private static object lockObj = new object();
//任务队列
private static Queue<ThreadStart> threadStartQueue = new Queue<ThreadStart>();
//记录当前工作的任务集合,从中可以判断当前工作线程使用数,如果使用int判断的话可能会有问题,
//用集合的话还能取得对象的引用,比较好
private static HashSet<ThreadStart> threadsWorker = new HashSet<ThreadStart>();
//当前允许最大工作线程数
private static int maxThreadWorkerCount = 1;
//当前允许最小工作线程数
private static int minThreadWorkerCount = 0;
/// <summary>
/// 设定最大工作线程数
/// </summary>
/// <param name="maxThreadCount">数量</param>
public static void SetMaxWorkThreadCount(int maxThreadCount)
{
maxThreadWorkerCount =minThreadWorkerCount>maxThreadCount?
minThreadWorkerCount : maxThreadCount;
}
/// <summary>
/// 设定最小工作线程数
/// </summary>
/// <param name="maxThreadCount">数量</param>
public static void SetMinWorkThreadCount(int minThreadCount)
{
minThreadWorkerCount = minThreadCount > maxThreadWorkerCount ?
maxThreadWorkerCount : minThreadCount;
}
/// <summary>
/// 启动线程池工作
/// </summary>
/// <param name="threadStartArray">任务数组</param>
public static void MyQueueUserWorkItem(ThreadStart[] threadStartArray)
{
//将任务集合都放入到线程池中
AddAllThreadsToPool(threadStartArray);
//线程池执行任务
ExcuteTask();
}
/// <summary>
/// 将单一任务加入队列中
/// </summary>
/// <param name="ts">单一任务对象</param>
private static void AddThreadToQueue(ThreadStart ts)
{
lock (lockObj)
{
threadStartQueue.Enqueue(ts);
}
}
/// <summary>
/// 将多个任务加入到线程池的任务队列中
/// </summary>
/// <param name="threadStartArray">多个任务</param>
private static void AddAllThreadsToPool(ThreadStart[] threadStartArray)
{
foreach (var threadStart in threadStartArray)
AddThreadToQueue(threadStart);
}
/// <summary>
/// 执行任务,判断队列中的任务数量是否大于0,如果是则判断当前正在使用的工作线程的
/// 数量是否大于等于允许的最大工作线程数,如果一旦有线程空闲的话
/// 就会执行ExcuteTaskInQueen方法处理任务
/// </summary>
private static void ExcuteTask()
{
while (threadStartQueue.Count > 0)
{
if (threadsWorker.Count < maxThreadWorkerCount)
{
ExcuteTaskInQueen();
}
}
}
/// <summary>
/// 执行出对列的任务,加锁保护
/// </summary>
private static void ExcuteTaskInQueen()
{
lock (lockObj)
{
ExcuteTaskByThread(
threadStartQueue.Dequeue());
}
}
/// <summary>
/// 实现细节,这里使用BackGroudWork来实现后台线程
/// 注册doWork和Completed事件,当执行一个任务前,前将任务加入到
/// 工作任务集合(表示工作线程少了一个空闲),一旦RunWorkerCompleted事件被触发则将任务从工作
/// 任务集合中移除(表示工作线程也空闲了一个)
/// </summary>
/// <param name="threadStart"></param>
private static void ExcuteTaskByThread(ThreadStart threadStart)
{
threadsWorker.Add(threadStart);
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, e) => { threadStart.Invoke(); };
worker.RunWorkerCompleted += (o, e) => { threadsWorker.Remove(threadStart); };
worker.RunWorkerAsync();
}
}
显示结果:
Asp.net异步IHttpAsyncHandler示例
有时我们需要使用IHttpAsyncHandler来异步实现一些特定的功能,让我用很简单的示例来阐述这个过程
1:首先编写Handler1的逻辑,注意要继承IHttpAsyncHandler接口
/// <summary>
/// 异步IHttpHandler,实现了一个简单的统计流量的功能,
/// 由于是示例代码,所以没有判断IP或者MAC
/// </summary>
public class Handler1 : IHttpAsyncHandler
{
//默认访问量是0
static int visitCount = 0;
/// <summary>
/// 这个HttpHandler的同步方法
/// </summary>
/// <param name="context"></param>
public void ProcessRequest(HttpContext context)
{
}
public bool IsReusable
{
get
{
return false;
}
}
/// <summary>
/// 实现IHttpAsyncHandler 接口方法
/// </summary>
/// <param name="context">当前HttpContext</param>
/// <param name="cb">回调函数</param>
/// <param name="extraData"></param>
/// <returns></returns>
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
//这里可以加上判断IP或mac的方法
visitCount++;
//实例化AsyncUserVisiteCounterResult对象
AsyncUserVisiteCounterResult result = new AsyncUserVisiteCounterResult(cb, visitCount, context);
result.Display();
return result;
}
/// <summary>
/// 结束本次IHttpAsyncHandler工作时触发的request方法
/// </summary>
/// <param name="result"></param>
public void EndProcessRequest(IAsyncResult result)
{
}
}
/// <summary>
/// 自定义IAsyncResult 实现我们额外的Display方法
/// </summary>
public class AsyncUserVisiteCounterResult : IAsyncResult
{
//回调参数
private object _param;
//是否异步执行完成
private bool _asyncIsComplete;
//回调方法
private AsyncCallback _callBack;
//当前上下文
private HttpContext _context;
public AsyncUserVisiteCounterResult(AsyncCallback callBack, object stateParam, HttpContext context)
{
this._callBack = callBack;
this._param = stateParam;
_asyncIsComplete = false;
this._context = context;
}
public object AsyncState
{
get { return this._param; }
}
/// <summary>
/// 等待句柄用于同步功能,关于等待句柄会在后续章节陈述
/// </summary>
public System.Threading.WaitHandle AsyncWaitHandle
{
get
{
return null;
}
}
/// <summary>
/// 该属性表示不需要异步任务同步完成
/// </summary>
public bool CompletedSynchronously
{
get { return false; }
}
/// <summary>
/// 该属性表示异步任务是否已经执行完成
/// </summary>
public bool IsCompleted
{
get
{
return this._asyncIsComplete;
}
}
/// <summary>
/// 自定义的额外功能,需要注意的是,执行完异步功能后
/// 要将_asyncIsComplete设置为true表示任务执行完毕而且
/// 执行回调方法,否则异步工作无法结束页面会卡死
/// </summary>
public void Display()
{
//这里先不用waitHandle句柄来实现同步
lock (this)
{
this._context.Response.Write("你是第" + (int)this._param + "位访问者,访问时间:"+DateTime.Now.ToString());
this._asyncIsComplete = true;
this._callBack(this);
}
}
}
2 在web.config中添加相应的配置,注意path指的是.ashx所在的路径,指的是相应的文件类型
<httpHandlers>
<add verb="*" path="AsyncThreadInAsp.net.Handler1.ashx" type="AsyncThreadInAsp.net.Handler1"/>
</httpHandlers>
3 最后在页面中访问
9 本章总结
本章详细介绍了CLR线程池和异步线程的一些概念和使用方法,包括线程池的优点和细节,异步的执行过程和重要元素等等,在下一章节中