在学习异步之前先来说说异步的好处,例如对于不需要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 ExecutorServic线程池(异步)

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

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

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

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

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

  5. C# 线程池异步调用

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

  6. java多线程系类:JUC线程池:05之线程池原理(四)(转)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

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

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

  8. java多线程系类:JUC线程池:02之线程池原理(一)

    在上一章"Java多线程系列--"JUC线程池"01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我 ...

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

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

随机推荐

  1. 部署DTCMS到Jexus遇到的问题及解决思路---Linux环境搭建

    最近朋友托我帮忙研究如何把一个DTCMS部署到Linux下,经过1天的研究,部署基本成功,可能有些细节还未注意到,现在把心得分享一下.过程比预期的要简单 身为.Net程序员,这个问题的第一步可能就是如 ...

  2. angularjs通过ng-change和watch两种方式实现对表单输入改变的监控

    angularjs通过ng-change和watch两种方式实现对表单输入改变的监控 直接上练习代码 <!DOCTYPE html> <html xmlns="http:/ ...

  3. C#实现接口IHttpModule完成统一的权限验证

    测试代码如下: using System; using System.Collections.Generic; using System.Text; using System.Collections; ...

  4. 以杨辉三角为例,从内存角度简单分析C语言中的动态二维数组

    学C语言,一定绕不过指针这一大难关,而指针最让人头疼的就是各种指向关系,一阶的指针还比较容易掌握,但一旦阶数一高,就很容易理不清楚其中的指向关系,现在我将通过杨辉三角为例,我会用四种方法从内存的角度简 ...

  5. 【Java POI】1、Java POI的使用

    很多时候,一个软件应用程序需要生成Microsoft Excel文件格式的报告.有时,一个应用程序甚至希望将Excel文件作为输入数据.例如,一个公司开发的应用程序将财务部门需要所有输出生成自己的Ex ...

  6. 两个inline-block消除间距和对齐(vertical-align)

    一.神奇的两个inline-block 很初级的问题,无聊决定写一个故事. 故事的主人公很简单,两个inline-block元素.代码如下,为了看起来简单明了,写得很简陋.效果图如右.发现有两个问题. ...

  7. LeanCloud云引擎相关问题

    (1).Windows 用户可以在 Github releases 页面 根据操作系统版本下载最新的 32 位 或 64 位 msi 安装包进行安装,安装成功之后在 Windows 命令提示符(或 P ...

  8. C# winform三种定时方法

    1. 直接用winform 的 timers 拖控件进去 代码 public partial class Form1 : Form     {         public Form1()       ...

  9. JavaScript大杂烩15 - 使用JQuery(下)

    前面我们总结了使用各种selector拿到了jQuery对象了,下面就是对这个对象执行指定的行为了. 2. 操作对象 - 行为函数action 执行jQuery内置的行为函数的时候,JQuery自动遍 ...

  10. kNN处理iris数据集-使用交叉验证方法确定最优 k 值

    基本流程: 1.计算测试实例到所有训练集实例的距离: 2.对所有的距离进行排序,找到k个最近的邻居: 3.对k个近邻对应的结果进行合并,再排序,返回出现次数最多的那个结果. 交叉验证: 对每一个k,使 ...