多线程之旅六——异步编程模式,自己实现IAsyncResult
发布日期:2022-04-11 08:52:47 浏览次数:6 分类:博客文章

本文共 6184 字,大约阅读时间需要 20 分钟。

作为模式,只是一种大家认可的经验,模式可以作为大家交流的词汇而存在。下面我们就要介绍几种异步编程模式,AMP、EAP和TAP。当然,法无定法,在我们理解的基础上,我们可以根据具体情况适度修改。下面介绍的只是在通常情况下的两种模式会是以什么样子的形式出现。

 

一 模型简介

1.APM 异步编程模型

这种模式的特征是一些成对出现的方法,分别以Begin和End作为前缀。
2.EAP 基于事件的异步模式
这个模式通常用于面向UI的组件,其中这些组件必须和进度报告和取消操作关联起来。方法有Async前缀。这种模式通常实现起来更为复杂,并且会带来一些额外开销,比如要由线程池线程转换到UI线程。

3.TAP 基于任务的模式

 

二 AMP模型

AMP的实现过程可以分为3个步骤,前两个步骤很明显,就是

1.编写BeginXXX方法,例如

public IAsyncResult BeginWork(int sleepyTime, AsyncCallback callback, object state)

最后两个参数是模式必须的,一个代表完成时的回调,一个代表要传递给回调的对象。因为AsyncCallback的定义是这样的

public delegate void AsyncCallback (  IAsyncResult ar)

所以你没办法直接给回调方法传递自己想要的参数,只能通过state来间接的传递给回调方法。

2.编写EndXXX方法

注意,EndXXX方法不等于回调方法,EndFoo方法应该是在回调方法里被调用来获取执行结果,这是刚接触的学习者常弄混的概念。它返回的结果应该和同步的XXX方法是一样的。下面展示了一个IAsyncResult的End方法的通常实现方式:

public T End()    {        // Wait for the work to finish, if it hasn't already.        if (_isCompleted == 0)        {            _asyncWaitHandle.WaitOne();            _asyncWaitHandle.Close();        }        // Propagate any exceptions or return the result.        if (_exception != null)            throw _exception;        return _result;    }

 

由于异步会立即返回,因此立即返回的对象是一个实现了IAsyncResult接口的对象,不是我们真正所需要的结果。那么AMP如何获取我们真正想要的结果呢?有下面这四种形式:

  • 1.从应用程序的主线程调用EndXXX方法,阻止应用程序执行,直到操作完成之后再继续执行。比如:

IAsyncResult ar = foo.BeginWork(null, null);var result = foo.EndWork(ar);

 

  • 2.使用 AsyncWaitHandle 来阻止应用程序执行,直到一个或多个操作完成之后再继续执行。

  • 3.按以下方式轮询操作完成状态:定期检查 IsCompleted 属性,操作完成后调用 End方法。

  • 4.使用 AsyncCallback 委托来指定操作完成时要调用的方法。

上面这几种方式都和一个实现了IAsyncResult接口的对象有关,所以第三步就是

3.编写一个实现IAsyncResult接口的类把BeginXXX和EndXXX这两个方法关联起来。

 需要注意的是,前面三种方式主要利用了内核同步对象,会引起线程阻塞。我们主要关注第四种以回调的形式,这才是APM最常用的方式。

 

我们打算自己实现以下IAsyncResult来更好的说明这四种方式, 在动手实现之前,先看看IAsyncResult接口的定义

public interface IAsyncResult{    object AsyncState { get; }    WaitHandle AsyncWaitHandle { get; }    bool CompletedSynchronously { get; }    bool IsCompleted { get; }}

 

实现IAsyncResult 

public delegate T Func
();public class SimpleAsyncResult
: IAsyncResult{ // All of the ordinary async result state. private volatile int _isCompleted; // 0==not complete, 1==complete. private readonly ManualResetEvent _asyncWaitHandle; private readonly AsyncCallback _callback; private readonly object _asyncState; private Exception _exception; private T _result; public SimpleAsyncResult( Func
work, AsyncCallback callback, object state) { _callback = callback; _asyncState = state; _asyncWaitHandle = new ManualResetEvent(false); RunWorkAsynchronously(work); } public bool IsCompleted { get { return (_isCompleted == 1); } } public WaitHandle AsyncWaitHandle { get { return _asyncWaitHandle; } } public object AsyncState { get { return _asyncState; } } // Runs the work on the thread pool, capturing exceptions, // results, and signaling completion. private void RunWorkAsynchronously(Func
work) { ThreadPool.QueueUserWorkItem(delegate { try { _result = work(); } catch (Exception e) { _exception = e; } finally { // Signal completion in the proper order: _isCompleted = 1; _asyncWaitHandle.Set(); if (_callback != null) _callback(this); } }); } public T End() { // Wait for the work to finish, if it hasn't already. if (_isCompleted == 0) { _asyncWaitHandle.WaitOne(); _asyncWaitHandle.Close(); } // Propagate any exceptions or return the result. if (_exception != null) throw _exception; return _result; }}

 

 

如何使用我们上面所编写的类来实现异步方法:

public class SimpleAsyncOperation    {        public int Work(int sleepyTime)        {            Thread.Sleep(sleepyTime);            return new Random().Next();        }        public IAsyncResult BeginWork(            int sleepyTime, AsyncCallback callback, object state)        {            return new SimpleAsyncResult
( delegate { return Work(sleepyTime); }, callback, state ); } public int EndWork(IAsyncResult asyncResult) { SimpleAsyncResult
simpleResult = asyncResult as SimpleAsyncResult
; if (simpleResult == null) throw new ArgumentException("Bad async result."); return simpleResult.End(); } }

 

构造函数的参数包含了一个Func<T>委托,这个委托表示需要通过异步方式来完成的工作。我们在私有方法RunWorkAsynchronously中使用线程池来实现了异步运行,并且是在一个try块中运行。如果执行成功,那么返回值将被保存在对象的_result对象中;如果执行在过程中抛出一个异常,那么这个异常将被保存在_exception域中。我们要确保这个异常不会超出Catch块的范围,否则将在线程池上导致一个未处理异常,这将使进程崩溃。

可以看到,EndWork的实现里调用simpleResult.End()方法,而simpleResult.End的实现里首先判断是否完成了,如果没完成则调用内核对象阻塞线程。

public T End()    {        // Wait for the work to finish, if it hasn't already.        if (_isCompleted == 0)        {            _asyncWaitHandle.WaitOne();            _asyncWaitHandle.Close();        }        // Propagate any exceptions or return the result.        if (_exception != null)            throw _exception;        return _result;    }

所以正确的调用EndWork的时机应该是在回调方法里,而不应该直接调用。当然,在我们的实现里为了简单起见,直接实例化了一个ManualResetEvent类型的_asyncWaitHandle,因此就算在回调方法里调用了EndWork方法,仍然会创建一个内核对象。

_isCompleted = 1;                _asyncWaitHandle.Set();                if (_callback != null)                    _callback(this);

 更高效的实现是只有在直接调用了AsyncWaitHandle属性或者在isCompleted = 1之前调用了EndXXX方法时,才应该初始化_asyncWaitHandle。

 

.NET Framework中常用的一些APM

1.委托

所有的委托类型除了提供Invoke方法外,还要提供一个BeginInvoke方法。但是应尽量避免使用,异步委托的实现并不高效。

 

2.System.IO.Stream

基类Stream类中为BeginWrtie,BeginRead提供了一个默认的实现,因此所有它的子类都自动有相应的方法。大多数的Stream,比如经常用到的FileStream,都重写了这个默认的实现,依赖非托管的Windows Overlapped I/O获取更高的效率。

 

3.System.Net.WebRequest

该类并没有实现BeginGetResponse,BeginGetRequestStream这些方法,因此将抛出一个NotImplementedException,但它的子类如HttpWebRequest等,都提供了实现。

 

总结:

APM是用于更低层次框架和库组件,在这些环境中注重的是灵活性而不是完成事件的发生方式(当然,我们通常都是用的是回调的方式)。通常一般应用层开发人员并不需要关心细节和细粒度的控制,而是希望简单的能够在某个事件完成后调用一些方法,或者更方便的回到GUI线程,这时EAP是一种不错的模式。

 

 

下一篇介绍EAP和UI线程以及UI编程模式的相关知识,过渡到IOS开发了。

转载地址:https://www.cnblogs.com/lwzz/archive/2012/11/03/2737775.html 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:多线程之旅:解读async和await
下一篇:多线程之旅二——线程与进程简介

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年03月18日 00时14分51秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

15拆解_收藏:15款劲芯微芯片无线充产品拆解 2019-04-21
弹出u盘_都说:U盘直接拔出不会丢失文件,“安全弹出”形同虚设,对吗? 2019-04-21
怎么查看elementui版本_2021新年 Vue3.0 + Element UI 尝鲜小记 2019-04-21
adreno630gpu参数_小米8搭载Adreno 630图形处理器 比荣耀play上的GPU Turbo更成熟 2019-04-21
带bitlocker解密的pe_如何在PE下解锁bitlocker 2019-04-21
lj245a引脚功能图_谁找到74254,74LS245芯片引脚的功能和功能图啊? 2019-04-21
sts 创建webservice项目_通过eclipse将Java生成webservice | 学步园 2019-04-21
python数字字符串和数字相加_数字和字符串 2021-06-24
python风控模型举例_一文搞定风控模型6大核心指标(附代码) 2021-06-24
java arraylist 写入文件_java-将自定义对象的ArrayList写入文件 2019-04-21
ice glacier2 java_ICE提纲之demo/Glacier2/callback(跨网回调) 2019-04-21
java 转发上传文件_java 后台请求其他接口转发文件 2019-04-21
Java get set 同步_java – getResultSet()“每个结果只能调用一次” 2019-04-21
java jmx 配置_为什么在配置JMX时Java打开3个端口? 2019-04-21
java thread回调_使用Runnable在Java中实现回调 2019-04-21
java 内存区_Java内存模型和Java内存区域的区别和联系? 2019-04-21
java定时任务监控_Spring定时任务使用及如何使用邮件监控服务器 2019-04-21
java crc32 使用_Java CRC32的用法 2019-04-21
java读取unicode_java怎么样将unicode解码读取?Java读取本地文件进 2019-04-21
java.io.file()_Java File getUsableSpace()方法 2019-04-21