在前面的《基于任务的异步编程模式(TAP)》文章中讲述了.net 4.5框架下的异步操作自我实现方式,实际上,在.net 4.5中部分类已实现了异步封装。如在.net 4.5中,Stream类加入了Async方法,所以基于流的通信方式都可以实现异步操作。

1、异步读取文件数据

  1. public static void TaskFromIOStreamAsync(string fileName)
  2. {
  3. int chunkSize = ;
  4. byte[] buffer = new byte[chunkSize];
  5.  
  6. FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, true);
  7.  
  8. Task<int> task = fileStream.ReadAsync(buffer, , buffer.Length);
  9. task.ContinueWith((readTask) =>
  10. {
  11. int amountRead = readTask.Result;
  12. //必须在ContinueWith中释放文件流
  13. fileStream.Dispose();
  14. Console.WriteLine($"Async(Simple) Read {amountRead} bytes");
  15. });
  16. }

  上述代码中,异步读取数据只读取了一次,完成读取后就将执行权交还主线程了。但在真实场景中,需要从流中读取多次才能获得全部的数据(如文件数据大于给定缓冲区大小,或处理来自网络流的数据(数据还没全部到达机器))。因此,为了完成异步读取操作,需要连续从流中读取数据,直到获取所需全部数据。

  上述问题导致需要两级Task来处理。外层的Task用于全部的读取工作,供调用程序使用。内层的Task用于每次的读取操作。

  第一次异步读取会返回一个Task。如果直接返回调用Wait或者ContinueWith的地方,会在第一次读取结束后继续向下执行。实际上是希望调用者在完成全部读取操作后才执行。因此,不能把第一个Task发布会给调用者,需要一个“伪Task”在完成全部读取操作后再返回。

  上述问题需要使用到TaskCompletionSource<T>类解决,该类可以生成一个用于返回的“伪Task”。当异步读取操作全部完成后,调用其对象的TrySetResult,让Wait或ContinueWith的调用者继续执行。

  1. public static Task<long> AsynchronousRead(string fileName)
  2. {
  3. int chunkSize = ;
  4. byte[] buffer = new byte[chunkSize];
  5. //创建一个返回的伪Task对象
  6. TaskCompletionSource<long> tcs = new TaskCompletionSource<long>();
  7.  
  8. MemoryStream fileContents = new MemoryStream();//用于保存读取的内容
  9. FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, true);
  10. fileContents.Capacity += chunkSize;//指定缓冲区大小。好像Capacity会自动增长,设置与否没关系,后续写入多少数据,就增长多少
  11.  
  12. Task<int> task = fileStream.ReadAsync(buffer, , buffer.Length);
  13. task.ContinueWith(readTask => ContinueRead(readTask, fileStream, fileContents, buffer, tcs));
  14. //在ContinueWith中循环读取,读取完成后,再返回tcs的Task
  15. return tcs.Task;
  16. }
  17.  
  18. /// <summary>
  19. /// 继续读取数据
  20. /// </summary>
  21. /// <param name="task">读取数据的线程</param>
  22. /// <param name="fileStream">文件流</param>
  23. /// <param name="fileContents">文件存放位置</param>
  24. /// <param name="buffer">读取数据缓存</param>
  25. /// <param name="tcs">伪Task对象</param>
  26. private static void ContinueRead(Task<int> task, FileStream fileStream, MemoryStream fileContents, byte[] buffer, TaskCompletionSource<long> tcs)
  27. {
  28. if (task.IsCompleted)
  29. {
  30. int bytesRead = task.Result;
  31. fileContents.Write(buffer, , bytesRead);//写入内存区域。似乎Capacity会自动增长
  32. if (bytesRead > )
  33. {
  34. //虽然看似是一个新的任务,但是使用了ContinueWith,所以使用的是同一个线程。
  35. //没有读取完,开启另一个异步继续读取
  36. Task<int> newTask = fileStream.ReadAsync(buffer, , buffer.Length);
  37. //此处做了一个循环
  38. newTask.ContinueWith(readTask => ContinueRead(readTask, fileStream, fileContents, buffer, tcs));
  39. }
  40. else
  41. {
  42. //已经全部读取完,所以需要返回数据
  43. tcs.TrySetResult(fileContents.Length);
  44. fileStream.Dispose();
  45. fileContents.Dispose();//应该是在使用了数据之后才释放数据缓冲区的数据
  46. }
  47. }
  48. }

2、适应Task的异步编程模式

  .NET Framework中的旧版异步方法都带有“Begin-”和“End-”前缀。这些方法仍然有效,为了接口的一致性,它们可以被封装到Task中。

  FromAsyn方法把流的BeginRead和EndRead方法作为参数,再加上存放数据的缓冲区。BeginRead和EndRead方法会执行,并在EndRead完成后调用Continuation Task,把控制权交回主代码。上述例子会关闭流并返回转换的数据

  1. const int ReadSize = ;//16k
  2.  
  3. /// <summary>
  4. /// 从文件中获取字符串
  5. /// </summary>
  6. /// <param name="path">文件路径</param>
  7. /// <returns>字符串</returns>
  8. public static Task<string> GetStringFromFile(string path)
  9. {
  10. FileInfo file = new FileInfo(path);
  11. byte[] buffer = new byte[file.Length];//存放数据的缓冲区
  12.  
  13. FileStream fileStream = new FileStream(
  14. path, FileMode.Open, FileAccess.Read, FileShare.None, buffer.Length,
  15. FileOptions.DeleteOnClose | FileOptions.Asynchronous);
  16.  
  17. Task<int> task = Task<int>.Factory.FromAsync(fileStream.BeginRead, fileStream.EndRead,
  18. buffer, , ReadSize, null);//此参数为BeginRead需要的参数
  19.  
  20. TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
  21.  
  22. task.ContinueWith(taskRead => OnReadBuffer(taskRead, fileStream, buffer, , tcs));
  23.  
  24. return tcs.Task;
  25. }
  26.  
  27. /// <summary>
  28. /// 读取数据
  29. /// </summary>
  30. /// <param name="taskRead">读取任务</param>
  31. /// <param name="fileStream">文件流</param>
  32. /// <param name="buffer">读取数据存放位置</param>
  33. /// <param name="offset">读取偏移量</param>
  34. /// <param name="tcs">伪Task</param>
  35. private static void OnReadBuffer(Task<int> taskRead, FileStream fileStream, byte[] buffer, int offset, TaskCompletionSource<string> tcs)
  36. {
  37. int readLength = taskRead.Result;
  38. if (readLength > )
  39. {
  40. int newOffset = offset + readLength;
  41. Task<int> task = Task<int>.Factory.FromAsync(fileStream.BeginRead, fileStream.EndRead,
  42. buffer, newOffset, Math.Min(buffer.Length - newOffset, ReadSize), null);
  43.  
  44. task.ContinueWith(callBackTask => OnReadBuffer(callBackTask, fileStream, buffer, newOffset, tcs));
  45. }
  46. else
  47. {
  48. tcs.TrySetResult(System.Text.Encoding.UTF8.GetString(buffer, , buffer.Length));
  49. fileStream.Dispose();
  50. }
  51. }

3、使用async 和 await方式读取数据

  下面的示例中,使用了async和await关键字实现异步读取一个文件的同时进行压缩并写入另一个文件。所有位于await关键字之前的操作都运行于调用者线程,从await开始的操作都是在Continuation Task中运行。但有无法使用这两个关键字的场合:①Task的结束时机不明确时;②必须用到多级Task和TaskCompletionSource时

  1. /// <summary>
  2. /// 同步方法的压缩
  3. /// </summary>
  4. /// <param name="lstFiles">文件清单</param>
  5. public static void SyncCompress(IEnumerable<string> lstFiles)
  6. {
  7. byte[] buffer = new byte[];
  8. foreach(string file in lstFiles)
  9. {
  10. using (FileStream inputStream = File.OpenRead(file))
  11. {
  12. using (FileStream outputStream = File.OpenWrite(file + ".compressed"))
  13. {
  14. using (System.IO.Compression.GZipStream compressStream = new System.IO.Compression.GZipStream(outputStream, System.IO.Compression.CompressionMode.Compress))
  15. {
  16. int read = ;
  17. while((read=inputStream.Read(buffer,,buffer.Length))>)
  18. {
  19. compressStream.Write(buffer, ,read);
  20. }
  21. }
  22. }
  23. }
  24. }
  25. }
  26.  
  27. /// <summary>
  28. /// 异步方法的文件压缩
  29. /// </summary>
  30. /// <param name="lstFiles">需要压缩的文件</param>
  31. /// <returns></returns>
  32. public static async Task AsyncCompress(IEnumerable<string> lstFiles)
  33. {
  34. byte[] buffer = new byte[];
  35. foreach(string file in lstFiles)
  36. {
  37. using (FileStream inputStream = File.OpenRead(file))
  38. {
  39. using (FileStream outputStream = File.OpenWrite(file + ".compressed"))
  40. {
  41. using (System.IO.Compression.GZipStream compressStream = new System.IO.Compression.GZipStream(outputStream, System.IO.Compression.CompressionMode.Compress))
  42. {
  43. int read = ;
  44. while ((read = await inputStream.ReadAsync(buffer, , buffer.Length)) > )
  45. {
  46. await compressStream.WriteAsync(buffer, , read);
  47. }
  48. }
  49. }
  50. }
  51. }
  52. }

使用Task实现非阻塞式的I/O操作的更多相关文章

  1. 并发式IO的解决方案:多路非阻塞式IO、多路复用、异步IO

    在Linux应用编程中的并发式IO的三种解决方案是: (1) 多路非阻塞式IO (2) 多路复用 (3) 异步IO 以下代码将以操作鼠标和键盘为实例来演示. 1. 多路非阻塞式IO 多路非阻塞式IO访 ...

  2. Swing做的非阻塞式仿飞秋聊天程序

    采用Swing 布局 NIO非阻塞式仿飞秋聊天程序, 切换皮肤颜色什么的小功能以后慢慢做 启动主程序. 当用户打开主程序后自动获取局域网段IP可以在 设置 --> IP网段过滤, 拥有 JMF ...

  3. 阻塞式和非阻塞式IO

    有很多人把阻塞认为是同步,把非阻塞认为是异步:个人认为这样是不准确的,当然从思想上可以这样类比,但方式是完全不同的,下面说说在JAVA里面阻塞IO和非阻塞IO的区别 在JDK1.4中引入了一个NIO的 ...

  4. Java IO(3)非阻塞式输入输出(NIO)

    在上篇<Java IO(2)阻塞式输入输出(BIO)>的末尾谈到了什么是阻塞式输入输出,通过Socket编程对其有了大致了解.现在再重新回顾梳理一下,对于只有一个“客户端”和一个“服务器端 ...

  5. Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程

    Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程 缓冲区(Buffer) 用于存储数据 通道(Channel) 用于传输数据 多路复用器(Selector) 用于轮询 Channel 状 ...

  6. Java基础知识强化之多线程笔记07:同步、异步、阻塞式、非阻塞式 的联系与区别

    1. 同步: 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就必须先得到返回值了. 换句话话说,调用者主动等待这个"调用"的结果. 对于 ...

  7. Java基础——NIO(二)非阻塞式网络通信与NIO2新增类库

    一.NIO非阻塞式网络通信 1.阻塞与非阻塞的概念  传统的 IO 流都是阻塞式的.也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在 ...

  8. 为什么IO多路复用需要采用非阻塞式IO

    近段时间开始学习<Unix网络编程>,代码实现了一个简单的IO多路复用+阻塞式的服务端,在学习了非阻塞式IO后,有一个疑问,即: 假如调用了select,并且关注了几个描述字,当关注的描述 ...

  9. 非阻塞式I/O

    套接字的默认状态是阻塞的.这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待相应的操作完成.可能阻塞的套接字调用可分为以下4类 (1)输入操作,包括read,readv,recv ...

随机推荐

  1. Python标准库(3.x): itertools库扫盲

    itertools functions accumulate() compress() groupby() starmap() chain() count() islice() takewhile() ...

  2. InstallUtil.exe版本引起安装windows services 服务遇到的问题,System.BadImageFormatException

    原文:把程序安装成windows服务的过程及遇到的问题 做好了定时任务的程序,要把它放在服务器上,作为windows服务运行,也就是说,退出登录,用户注销后程序任然在后台运行. 将exe程序发布为服务 ...

  3. 【转载】动态载入DLL所需要的三个函数详解(LoadLibrary,GetProcAddress,FreeLibrary)

    原文地址:https://www.cnblogs.com/westsoft/p/5936092.html 动态载入 DLL 动态载入方式是指在编译之前并不知道将会调用哪些 DLL 函数, 完全是在运行 ...

  4. UWP开发-在UWP中使用sqlite

    原文:UWP开发-在UWP中使用sqlite sqlite是一种轻量级的数据库,对于一些资源紧张又需要数据库的开发非常好用. SQLite 是一个开源的无服务器嵌入式数据库. 这些年来,它已作为面向存 ...

  5. Delphi事件的广播

    原文地址:Delphi事件的广播 转作者:MondaySoftware 明天就是五一节了,辛苦了好几个月,借此机会应该尽情放松一番.可是想到Blog好久没有写文章,似乎缺些什么似的.这几个月来在项目中 ...

  6. visual studio添加docker支持简记

    很久以前学过一段时间的docker,那时装了电脑卡得受不了,学了一会就卸载了,最近电脑又装上了docker,感觉好像没有以前这么卡了,还是同一台电脑surface pro4, 试了一下visual s ...

  7. 跨越DLL边界传递CRT对象潜在的错误

    跨越DLL边界传递CRT对象潜在的错误 翻译:magictong(童磊)2013年5月 版权:microsoft 原文地址:http://msdn.microsoft.com/en-us/librar ...

  8. Spring之bean生命始末

    可以为Bean定制初始化后的生命行为,也可以为Bean定制销毁前的生命行为.举例:ba06包.首先,这些方法需要在Bean类中事先定义好:是方法名随意的public void方法. 其次,在配置文件的 ...

  9. Error: Cannot find module '@babel/runtime/core-js/object/keys'(npm start报错)

    1.问题描述: 在npm start启动react项目的时候,会出现Cannot find module '@babel/runtime/core-js/object/keys'的报错: 打开:项目根 ...

  10. Flink UDF

    本文会主要讲三种udf: ScalarFunction TableFunction AggregateFunction 用户自定义函数是非常重要的一个特征,因为他极大地扩展了查询的表达能力.本文除了介 ...