在学习异步之前先来说说异步的好处,例如对于不需要CPU参数的输入输出操作,可以将实际的处理步骤分为以下三步:

  1. 启动处理;
  2. 实际的处理,此时不需要CPU参数;
  3. 任务完成后的处理;

  以上步骤如果仅仅使用一个线程,当线程正在处理UI操作时就会出现“卡”的现象。

  如果使用异步的处理方式,则这三步处理过程涉及到两个线程,主线程中启动第一步;第一步启动后,主线程结束(如果不结束,只会让该线程处于无作为的等待状态);第二步不需要CPU参与;第二步完成之后,在第二个线程上启动第三步;完成之后第二个线程结束。这样的处理过程中没有一个线程需要处于等待状态,使得运行的线程得到充分利用。

一、CLR线程池的I/O线程

上一篇学习的都是CLR线程池的辅助线程,这次要学习的是CLR线程池的I/O线程。

I/O线程是.NET专为访问外部资源所设置的一种线程,因为访问外部资源常常要受到外界因素的影响,为了防止让主线程受影响而长期处于阻塞状态,.NET为多个I/O操作都建立起了异步方法。例如:FileStream、TCP/IP、WebRequest、WebService等等,而且每个异步方法的使用方式都非常类似,

  都是以Beginxxx开始,以Endxxx结束(APM)。对于APM来说,必须使用Endxxx结束异步,否则可能会造成资源泄露。Beginxxx实际是将线程排入线程池。

  另外还有一种基于事件的异步编程模式(EPM),支持基于事件的异步模式的类将有一个或多个后缀为Async的方法,同时还会有一个相应名为Completed后缀的事件,Async方法用于启动异步处理,而Completed事件将在异步处理完成之后通过事件来宣告异步处理的完成。注意,在使用EPM模式的时候,不管是完成了异步请求,还是在处理中出现异常,或者是终止异步处理,都必须要调用Compeleted处理程序。如:

  OpenReadAsync
  OpenReadCompleted

二、异步读写FileStream

需要在FileStream中异步调用I/O线程,必须使用以下构造函数建立FileStream对象,并把useAsync设置为true。

FileStream stream = new FileStream(string path,FileMode mode,FileAccess access,FileShare share,int bufferSize,bool useAsync);

  参数说明:

  1. path是文件的相对路径或绝对路径;
  2. mode确定如何打开或创建文件;
  3. access确认访问文件的方式;
  4. share确定文件如何进程共享;
  5. bufferSize是代表缓冲区大小,一般默认最小值为8,在启动异步读取或写入时,文件大小一般大于缓冲大小;
  6. userAsync代表是否启动异步I/O线程。

  注意:当使用BeginRead和BeginWrite方法在执行大量读或写时效果更好,但对于少量读/写,这些方法速度可能比同步还要慢,因为进行线程间的切换需要大量时间。

  1、异步写入

  FileStream中包含BeginWrite、EndWrite方法可以启动I/O线程进行异步写入。

  public override IAsyncResult BeginWrite(byte[] array,int offset,int numBytes,AsyncCallback,Object stateObject)
  public override void EndWrite(IAsyncResult asyncResult)

  BeginWrite返回值为IAsyncResult,使用方式与委托的BeginInvoke方法相似,最好就是使用回调函数,避免线程阻塞。

  最后两个参数还是同样的套路:

  • AsyncCallback用于绑定回调函数;
  • Object用于传递外部数据。

  要注意一点:AsyncCallback所绑定的回调函数必须是带单个IAsyncResult参数的无返回值方法。

  在例子中,把FileStream作为外部数据传递到回调函数当中,然后再回调函数中利用IAsyncResult.AsyncState获取FileStream对象,最后通过FileStream.EndWrite(IAsyncResult)结束写入。

  下面是一个异步写入的例子:

    class Program
{
static void Main(string[] args)
{
int a, b;
ThreadPool.GetMaxThreads(out a, out b);
Console.WriteLine("原有辅助线程数" + a + " " + "原有I/O线程数" + b); //文件名 文件创建方式 文件权限 文件进程共享 缓冲区大小为1024 是否启动异步I/O线程为true
FileStream stream = new FileStream(@"D:\123.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 1024, true);
//这里要注意,如果写入的字符串很小,则.Net会使用辅助线程写,因为这样比较快
byte[] bytes = Encoding.UTF8.GetBytes("你在他乡还好吗?");
//异步写入开始,倒数第二个参数指定回调函数,最后一个参数将自身传到回调函数里,用于结束异步线程
stream.BeginWrite(bytes, 0, (int)bytes.Length, new AsyncCallback(Callback), stream); ThreadPool.GetAvailableThreads(out a, out b);
Console.WriteLine("现有辅助线程数" + a + " " + "现有有I/O线程数" + b); Console.WriteLine("主线程继续干其他活!");
Console.ReadKey();
} static void Callback(IAsyncResult result)
{
//显示线程池现状
Thread.Sleep(2000);
//通过result.AsyncState再强制转换为FileStream就能够获取FileStream对象,用于结束异步写入
FileStream stream = (FileStream)result.AsyncState;
stream.EndWrite(result);
stream.Flush();
stream.Close();
}
}

  输出结果如下:

  

  对于结束异步线程的方法,还是玩IAsyncResult的这一套,在启动异步写时将自身对象传递到回调函数中,在回调函数中获得自身去结束异步线程。

  这就是C#中的异步操作,从剩余线程数我们看到,异步实际上是调用线程池的线程来实现异步的。

  2、异步读取

  FileStream中可以通过使用BeginRead和EndRead调用异步I/O线程读取:

  public override IAsyncResult BeginRead(byte[] array,int offset,int numBytes,AsyncCallback userCallback,Object stateObject)
  public override int EndRead(IAsyncResult asyncResult)

  BeginRead与EndRead方法与写相似,AsyncCallback用于绑定回调函数;Object用于传递外部数据。在回调函数只需要使用IAsyncResult.AsyncState就可以获取外部数据。EndRead方法会返回从流中读取到的字节数量。

  首先定义FileData类,里面包含FileStream对象,byte[]数组和长度。然后把FileData对象作为外部数据传到回调函数,在回调函数中,把IAsyncResult.AsyncState强制转换为FileData。然后通过FileStream.EndRead(IAsyncResult)结束读取。

  最后比较一下长度,如果读取到的长度与输入的数据长度不一致,则抛出异常。

    class Program
{
static void Main(string[] args)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
Console.WriteLine("原有辅助线程:" + a + "原有I/O线程:" + b); byte[] byteData = new byte[1024];
FileStream stream = new FileStream(@"D:\123.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 1024, true);
//把FileStream对象,byte[]对象,长度等有关数据绑定到FileDate对象中,以附带属性方式送到回调函数
Hashtable ht = new Hashtable();
ht.Add("Length", (int)stream.Length);
ht.Add("Stream", stream);
ht.Add("ByteData", byteData); //启动异步读取,倒数第二个参数是指定回调函数,倒数第一个参数是传入回调函数中的参数
stream.BeginRead(byteData, 0, (int)ht["Length"], new AsyncCallback(Completed), ht); ThreadPool.GetAvailableThreads(out a, out b);
Console.WriteLine("现有辅助线程:" + a + "现有I/O线程:" + b); Console.ReadKey();
} //实际参数就是回调函数
static void Completed(IAsyncResult result)
{
Thread.Sleep(2000);
//参数result实际上就是Hashtable对象,以FileStream.EndRead完成异步读取
Hashtable ht = (Hashtable)result.AsyncState;
FileStream stream = (FileStream)ht["Stream"];
int length = stream.EndRead(result);
stream.Close();
string str = Encoding.UTF8.GetString(ht["ByteData"] as byte[]);
Console.WriteLine(str);
stream.Close();
}
}

  输出如下:

  

  注意,如果文件过小,小于缓冲区1024,那么可能会调用工作者线程而非I/O线程操作。但是根据我的观察,只是读取文件时文件过小可能会调用辅助线程操作,但是写入时不会。

  像上面就是直接用辅助线程处理了。

  IAsyncResult的作用主要有两点:

  • AsyncState属性,用来传递参数到回调函数;
  • Endxxx方法,结束异步操作方法需要此对象作为参数;

三、异步WebRequest

  System.Net.WebRequest是.NET为实现Internet的"请求/响应模型"而开发的一个abstract基类。它主要有三个子类:

  • FtpWebRequest,FileWebRequest使用"file://路径"的URI方式实现对本地资源和内部文件的请求/响应;
  • HttpWebRequest,FtpWebRequest使用FTP文件传输协议实现文件请求/响应;
  • FileWebRequest,HttpWebRequest用于处理HTTP的页面请求/响应;

  当使用WebRequest.Create(string uri)创建对象时,应用程序就可以根据请求协议判断实现类来进行操作FileWebRequest、FtpWebRequest、HttpWebRequest各有其作用。由于使用方法类似,下面就用常用的HttpWebRequest为例子介绍一下异步WebRequest的使用方法。

  HttpWebRequest包含由一下几个常用方法处理请求/响应:

  public override Stream GetRequest()
public override WebResponse GetResponse()
public override IAsyncResult BeginGetRequestStream(AsyncCallback callback,Object state)
public override Stream EndGetRequestStream(IAsyncResult asyncResult)
public override IAsyncResult BeginGetResponse(AsyncCallback callback,Object state)
public override WebResponse EndGetResponse(IAsyncResult asyncResult)
  • BeginGetRequestStream、EndGetRequestStream用于异步向HttpWebRequest对象写入请求信息;
  • BeginGetResponse、EndGetResponse用于异步发送页面请求并获取返回信;

  使用异步方式操作Internet的"请求/响应",避免主线程长期处于等待状态,而操作期间异步线程是来自CLR线程池的I/O线程。

  注意:请求与响应不能使用同步与异步混合开发模式,即当请求写入使用GetRequestStream同步模式,即使响应使用BeginGetResponse异步方法,操作也与GetRequestStream方法在于同一线程内。

    class Program
{
static void Main(string[] args)
{
int a, b;
ThreadPool.GetAvailableThreads(out a, out b);
Console.WriteLine("原有辅助线程:" + a + "原有I/O线程:" + b); //使用WebRequest.Create方法建立HttpWebRequest对象
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("http://www.baidu.com");
webRequest.Method = "post"; //对写入数据的RequestStream对象进行异步请求
IAsyncResult result = webRequest.BeginGetResponse(new AsyncCallback(EndGetResponse), webRequest);
Thread.Sleep(1000);
ThreadPool.GetAvailableThreads(out a, out b);
Console.WriteLine("现有辅助线程:" + a + "现有I/O线程:" + b); Console.WriteLine("主线程继续干其他事!");
Console.ReadKey();
} static void EndGetResponse(IAsyncResult result)
{
Thread.Sleep(2000);
//结束异步请求,获取结果
HttpWebRequest webRequest = (HttpWebRequest)result.AsyncState;
WebResponse webResponse = webRequest.EndGetResponse(result); Stream stream = webResponse.GetResponseStream();
StreamReader sr = new StreamReader(stream);
string html = sr.ReadToEnd();
Console.WriteLine(html.Substring(0,50));
}
}

  显示结果如下:

  

四、异步SqlCommand

  使用异步SqlCommand的时候,请注意把ConnectionString 的 Asynchronous Processing 设置为 true 。

    class Program
{
static void Main(string[] args)
{
int a, b;
ThreadPool.GetMaxThreads(out a, out b);
Console.WriteLine("原有辅助线程数" + a + " " + "原有I/O线程数" + b); string str = "server=.;database=Test;uid=sa;pwd=123;Asynchronous Processing=true";
SqlConnection conn = new SqlConnection(str);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "INSERT INTO Person VALUES(15,'郭嘉',22)";
conn.Open();
cmd.BeginExecuteNonQuery(new AsyncCallback(EndCallback), cmd);
Thread.Sleep(1000);
ThreadPool.GetAvailableThreads(out a, out b);
Console.WriteLine("现有辅助线程数" + a + " " + "现有I/O线程数" + b); Console.WriteLine("主线程继续执行!"); Console.ReadKey();
} public static void EndCallback(IAsyncResult result)
{
Thread.Sleep(2000);
SqlCommand cmd = result.AsyncState as SqlCommand; //获得异步传入的参数
Console.WriteLine("成功执行命令:" + cmd.CommandText);
Console.WriteLine("本次执行影响行数为:" + cmd.EndExecuteNonQuery(result));
cmd.Connection.Close();
}
}

  输出如下:

  

线程池 异步I/O线程 <第三篇>的更多相关文章

  1. 转载 线程池 异步I/O线程 <第三篇>

    在学习异步之前先来说说异步的好处,例如对于不需要CPU参数的输入输出操作,可以将实际的处理步骤分为以下三步: 启动处理: 实际的处理,此时不需要CPU参数: 任务完成后的处理: 以上步骤如果仅仅使用一 ...

  2. java 线程池——异步任务

    一.简单粗暴的线程 最原始的方式,当我们要并行的或者异步的执行一个任务的时候,我们会直接使用启动一个线程的方式,如下面所示: new Thread(new Runnable() { @Override ...

  3. Java ExecutorServic线程池(异步)

    相信大家都在项目中遇到过这样的情况,前台需要快速的显示,后台还需要做一个很大的逻辑.比如:前台点击数据导入按钮,按钮后的服务端执行逻辑A,和逻辑B(执行大量的表数据之间的copy功能),而这时前台不能 ...

  4. 线程池,多线程,线程异步,同步和死锁,Lock接口

    线程池 线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源. 除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源.线程 ...

  5. C# 线程池异步调用

    许多应用程序使用多个线程,但这些线程经常在休眠状态中耗费大量的时间来等待事件发生.其他线程可能进入休眠状态,并且仅定期被唤醒以轮询更改或更新状态信息,然后再次进入休眠状态.为了简化对这些线程的管理,. ...

  6. 线程池如何复用一个线程-- ThreadPoolExecutor的实现(未完)

    任务是一组逻辑工作单元,而线程则是使任务异步执行的机制.在Java中,Runnable对象代表一个任务,Thread对象负责创建一个线程执行这个任务. 前提:1. 程序需要处理大量任务 2. 任务的执 ...

  7. C#线程学习笔记二:线程池中的工作者线程

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,记录一下学习过程以备后续查用. 一.线程池基础 首先,创 ...

  8. java多线程系类:JUC线程池:03之线程池原理(二)(转)

    概要 在前面一章"Java多线程系列--"JUC线程池"02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包 ...

  9. java多线程、线程池及Spring配置线程池详解

    1.java中为什么要使用多线程使用多线程,可以把一些大任务分解成多个小任务来执行,多个小任务之间互不影像,同时进行,这样,充分利用了cpu资源.2.java中简单的实现多线程的方式 继承Thread ...

随机推荐

  1. mybatis批量update(mysql)

    Mapper文件中的写法 <insert id="batchUpdateTjData"> <foreach collection="list" ...

  2. MySQL数据库配置主从服务器实现双机热备

    转自:http://www.cnblogs.com/cchun/p/3712637.html 一.安装MySQL 说明:在两台MySQL服务器192.168.21.169和192.168.21.168 ...

  3. HOJ 1096 Divided Product (DFS)

    时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 Given two positive integers N and M, please divide N into sev ...

  4. ZooKeeper架构设计及其应用

    ZooKeeper是一个开源的分布式服务框架,它是Apache Hadoop项目的一个子项目,主要用来解决分布式应用场景中存在的一些问题,如:统一命名服务.状态同步服务.集群管理.分布式应用配置管理等 ...

  5. poj 3662 Telephone Lines(好题!!!二分搜索+dijkstra)

    Description Farmer John wants to set up a telephone line at his farm. Unfortunately, the phone compa ...

  6. web前端之 CSS

    CSS概述 CSS 指层叠样式表 (Cascading Style Sheets),说白了就是给html代码穿上好看的衣服,让页面变得好看 CSS存在形式 1.在标签的属性中设置,优先级较高 代码如下 ...

  7. 给SharePoint页面加入自己定义页脚Custom footer

    给SharePoint页面加入自己定义页脚Custom footer         在公司做站点设计项目时,须要在页面上加入页脚.         非常多人都把页脚忽视了,认为没什么多大用处,事实上 ...

  8. Rhythmbox中文乱码解决的方法

    转自:http://hi.baidu.com/morgensonne/item/3470aef58747abde6325d2d9 今天在网络上找到了一个比較好的解决Rhythmbox中文乱码的问题的方 ...

  9. socket——本地服务器和android手机客户端通讯(防止中文乱码)

    线上效果图: 服务端接收到的. 客户端接受到服务器返回的. server端代码直接运行在本地就可以了. 手机客户端运行在手机上就行. 先安装客户端,再启动server.然后再输入文字,点击发送. se ...

  10. 我对 javascript 闭包的理解

    学js的学到闭包,但是理解不深. 后来看了一下这篇文章: 地址:http://leepiao.blog.163.com/blog/static/4850313020112835355917/ 内容如下 ...