案例研究:CopyToAsync
返回该系列目录《基于Task的异步模式--全面介绍》
把一个流拷贝到另一个流是有用且常见的操作。Stream.CopyTo 方法在.Net 4中就已经加入来满足要求这个功能的场景,例如在一个指定的URL处下载数据:
public static byte[] DownloadData(string url)
{
using(var request = WebRequest.Create(url))
using(var response = request.GetResponse())
using(var responseStream = response.GetResponseStream())
using(var result = new MemoryStream())
{
responseStream.CopyTo(result);
return result.ToArray();
}
}
为了提高响应能力和伸缩性,我们想使用基于TAP模式来实现上面的功能。可以尝试按下面的来做:
public static async Task<byte[]> DownloadDataAsync(string url)
{
using(var request = WebRequest.Create(url))
{
return await Task.Run(() =>
{
using(var response = request.GetResponse())
using(var responseStream = response.GetResponseStream())
using(var result = new MemoryStream())
{
responseStream.CopyTo(result);
return result.ToArray();
}
}
}
}
此实现如果用于UI线程会提升响应能力,因为它脱离了从网络流下载数据任务的调用线程以及把该网络流复制到最终将下载的数据转成一个数组的内存流。然而,该实现对伸缩性没有效果,因为它在等待数据下载的过程中,仍旧执行同步I/O和阻塞线程池线程。反之,我们想要的是下面的功能代码:
public static async Task<byte[]> DownloadDataAsync(string url)
{
using(var request = WebRequest.Create(url))
using(var response = await request.GetResponseAsync())
using(var responseStream = response.GetResponseStream())
using(var result = new MemoryStream())
{
await responseStream.CopyToAsync(result);
return result.ToArray();
}
}
不幸的是,在.Net 4中缺少异步的CopyToAsync方法,只有Stream类有一个同步的CopyTo方法。现在我们就自己提供一个实现:
public static void CopyTo(this Stream source, Stream destination)
{
var buffer = new byte[0x1000];
int bytesRead;
while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
{
destination.Write(buffer, 0, bytesRead);
}
}
为了提供一个异步的CopyTo实现,我们可以利用编译器实现TAP的能力,稍微地修改这个实现:
public static async Task CopyToAsync(this Stream source, Stream destination)
{
var buffer = new byte[0x1000];
int bytesRead;
while((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await destination.WriteAsync(buffer, 0, bytesRead);
}
}
这里我们将返回类型从void改成了Task,将Read和Write分别换成了ReadAsync和WriteAsync,并且在ReadAsync和WriteAsync的调用前加了与上下文相关的await关键字前缀。.Net 4 中不存在ReadAsycn和WriteAsync,但是可以通过基于Task.Factory.FromAsync实现,关于这个描述在上一篇随笔中的“Tasks和APM”章节讲过:
public static Task<int> ReadAsync(
this Stream source, byte [] buffer, int offset, int count)
{
return Task<int>.Factory.FromAsync(source.BeginRead, source.EndRead,
buffer, offset, count, null);
} public static Task WriteAsync(
this Stream destination, byte [] buffer, int offset, int count)
{
return Task.Factory.FromAsync(
destination.BeginWrite, destination.EndWrite,
buffer, offset, count, null);
}
有了这些方法,我们可以成功地实现CopyToAsync方法。我们也可以通过添加一个CancellationToken到方法中以支持撤销请求,该CancellationToken将会在复制过程中的每次读写之后被监控到(如果ReadAsync和WriteAsync支持撤销,那么也可以将CancellationToken线程化到那些调用中):
public static async Task CopyToAsync(
this Stream source, Stream destination,
CancellationToken cancellationToken)
{
var buffer = new byte[0x1000];
int bytesRead;
while((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await destination.WriteAsync(buffer, 0, bytesRead);
cancellationToken.ThrowIfCancellationRequested();
}
}
【注意这种撤销在同步的CopyTo实现中也是有用的,传入的CancellationToken会启用撤销。实现会依赖一个从该方法返回的可取消的对象,但实现接收到那么对象已经太晚了,因为同步调用完成时,已经没有留下要取消的东西了。】
我们也加入了进度通知的支持,包括至今已经复制了多少数据:
public static async Task CopyToAsync(
this Stream source, Stream destination,
CancellationToken cancellationToken,
IProgress<long> progress)
{
var buffer = new byte[0x1000];
int bytesRead;
long totalRead = 0;
while((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await destination.WriteAsync(buffer, 0, bytesRead);
cancellationToken.ThrowIfCancellationRequested();
totalRead += bytesRead;
progress.Report(totalRead);
}
}
有了该方法,我们现在可以完全实现我们的DownloadDataAsync方法了,包括加入撤销和进度支持:
public static async Task<byte[]> DownloadDataAsync(
string url,
CancellationToken cancellationToken,
IProgress<long> progress)
{
using(var request = WebRequest.Create(url))
using(var response = await request.GetResponseAsync())
using(var responseStream = response.GetResponseStream())
using(var result = new MemoryStream())
{
await responseStream.CopyToAsync(
result, cancellationToken, progress);
return result.ToArray();
}
}
给我们的CopyToAsync方法做进一步的优化也是可能的。比如,如果我们要使用两个buffer而不是一个,就可以在读取下一片数据时写入之前读取的数据,因此如果读取和写入都使用了异步了I/O就会产生交叉延迟:
public static async Task CopyToAsync(this Stream source, Stream destination)
{
int i = 0;
var buffers = new [] { new byte[0x1000], new byte[0x1000] };
Task writeTask = null;
while(true)
{
var readTask = source.ReadAsync(buffers[i], 0, buffers[i].Length))>0;
if (writeTask != null) await Task.WhenAll(readTask, writeTask);
int bytesRead = await readTask;
if (bytesRead == 0) break;
writeTask = destination.WriteAsync(buffers[i], 0, bytesRead);
i ^= 1; // swap buffers
}
}
消除不必要的上下文转换是另一个优化。正如之前提到的,默认await一个Task开始执行的时候,会传输回到当前的SynchronizationContext。在CopyToAsynch实现的情况下,使用这样的转换时没必要的,因为我们没有操作任何UI状态。我们可以发挥Task.ConfigureAwait的优势类关闭这个自动的转换。为了简化,上面的原始异步的实现修改如下:
public static Task CopyToAsync(this Stream source, Stream destination)
{
var buffer = new byte[0x1000];
int bytesRead;
while((bytesRead = await
source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{
await destination.WriteAsync(buffer, 0, bytesRead)
.ConfigureAwait(false);
}
}
返回该系列目录《基于Task的异步模式--全面介绍》
案例研究:CopyToAsync的更多相关文章
- Coursera 机器学习课程 机器学习基础:案例研究 证书
完成了课程1 机器学习基础:案例研究 贴个证书,继续努力完成后续的课程:
- Top100Summit全球案例研究峰会第一天总结——云计算和大数据
很荣幸受邀参加Top100Summit全球软件案例研究峰会,这次的大会主题是<技术推动商业变革>,组委会从全国投稿的460多件案例中甄选出100件具有代表价值的案例,进行为期4天的分享,第 ...
- JavaEE Tutorials (30) - Duke综合案例研究示例
30.1Duke综合应用的设计和架构456 30.1.1events工程458 30.1.2entities工程459 30.1.3dukes—payment工程461 30.1.4dukes—res ...
- JavaEE Tutorials (29) - Duke辅导案例研究示例
29.1Duke辅导应用的设计和架构44529.2主界面447 29.2.1主界面中使用的Java持久化API实体447 29.2.2主界面中使用的企业bean448 29.2.3主界面中使用的Web ...
- JavaEE Tutorials (28) - Duke书店案例研究示例
28.1Duke书店的设计和架构43828.2Duke书店接口439 28.2.1Book Java持久化API实体439 28.2.2Duke书店中使用的企业bean440 28.2.3Duke书店 ...
- 案例研究:Web应用出现间歇性的SqlException
案例研究:Web应用出现间歇性的SqlException 2013-07-29 14:36 by 微软互联网开发支持, 231 阅读, 3 评论, 收藏, 编辑 最近有客户找到我,说他们生产环境的事件 ...
- Netty实战十四之案例研究(一)
1.Droplr——构建移动服务 Bruno de Carvalho,首席架构师 在Droplr,我们在我的基础设施的核心部分.从我们的API服务器到辅助服务的各个部分都使用了Netty. 这是一个关 ...
- 《Java大学教程》—第11章 案例研究--第1部分
自测题:1. 图11-1的UML设计中各个类之间的关系.Hostel与TenantList是关联关系:TenantList和PaymentList与ObjectList是泛化关系.TenantL ...
- UML和模式应用3:迭代和进化式分析和设计案例研究
1.前言 如何进行迭代和进化式分析和设计?将采用案例研究的方式贯穿始终.案例研究所包含的内容: UI元素 核心应用逻辑层 数据库访问 与外部软硬构件的协作 本章关于OOA/D主要介绍核心应用逻辑层 2 ...
- 洞察行业领先者的前沿思想——第五届TOP100全球软件案例研究峰会精彩谢幕
(第五届TOP100summit开幕式现场) 12月09日-12日,由msup主办的第五届TOP100全球软件案例研究峰会(以下简称TOP100summit)在北京国家会议中心举行,作为互联网行业最有 ...
随机推荐
- 利用(Tcmalloc) google-perftools优化Nginx和MySQL性能
一.安装libunwind wget http://download.savannah.gnu.org/releases/libunwind/libunwind-1.1.tar.gz 本地下载:htt ...
- page、pageContext、servletContext的区别
ServletContext是容器上下文,指当前的一个web应用的上下文 JSP网页本身,page对象是当前页面转换后的Servlet类的实例.从转换后的Servlet类的代码中,可以看到这种关系:O ...
- Accordion - 手风琴
//手风琴效果 <div style="overflow:hidden;height:400px;width:948px;"> <div class=" ...
- [转]彻底征服Word 2007标题多级列表
[转]彻底征服Word 2007标题多级列表 用Word编写文档的人都知道,一篇长文档一般是需要分章节来划分段落的.在Word中也有对应的工具来完成这项任务,这就是多级列表.然而绝大多数使用Micro ...
- 微信小视频复制到手机本地Android APP 分享
因为需要将拍的宝宝的微信小视频上传到亲宝宝软件,每次去手动找文件比较麻烦,所以做了个微信视频复制到手机本地的APP,做工虽然粗糙,但是绝对实用, 下载地址 http://pan.baidu.com/s ...
- Windows 安装 openssl
http://slproweb.com/products/Win32OpenSSL.html File Type Description Win32 OpenSSL v1.1.0b Light 3MB ...
- css长度值与颜色值
颜色值 在网页中的颜色设置是非常重要的,有字体颜色,背景颜色,边框颜色等,设置颜色的方法也有很多种: 1.英文命令颜色 p{color:red;} 2.rgb颜色 p{color:rgb(133,45 ...
- wpa_supplicant代码走读
wpa_supplicant_add_iface wpa_supplicant_init_iface wpa_supplicant_set_driver wpa_config_read wpa_sup ...
- java多线程(精华版)
在 Java 程序中使用多线程要比在 C 或 C++ 中容易得多,这是因为 Java 编程语言提供了语言级的支持.本文通过简单的编程示例来说明 Java 程序中的多线程是多么直观.读完本文以后,用户应 ...
- go runtime.Gosched() 和 time.Sleep() 做协程切换
网上看到个问题: package main import ( "fmt" "time" ) func say(s string) { ; i < ; i+ ...