案例研究: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)在北京国家会议中心举行,作为互联网行业最有 ...
随机推荐
- H5前端性能测试快速入门
前言 说到H5测试,对于做WEB测试的同学来说再熟悉不过了,它包括页H5功能测试,前端性能测试,浏览器兼容性能测试,以及服务端性能测试.那本文谈到的则是H5前端性能测试,并希望通过阅读本文后,能够知道 ...
- 1236 - Pairs Forming LCM -- LightOj1236 (LCM)
http://lightoj.com/volume_showproblem.php?problem=1236 题目大意: 给你一个数n,让你求1到n之间的数(a,b && a<= ...
- ubuntu 14 中tomcat的开机启动设置
开机自启动,将要执行的语句写入/etc/rc.local. #!/bin/sh -e # # rc.local # # This script is executed at the end of ea ...
- 通过统计用户DNS解析记录,实现监控用户上网行为
上次通过扫描抓包分析TTL的方式检测公司网络开放的端口,发现没有开放53端口(DNS),也就是在公司内部的主机只能用服务器自动分配的DNS,并且发现这是台内部服务器.今天发现bing上不去,检测后发现 ...
- visual studio 2005 常用按键
VS2005 实用快捷键,迅速提高代码编写效率! 代码快捷键 Ctrl+J / Ctrl+K,L 列出成员 Ctrl+Shift+空格键 / Ctrl+K,P 参数信息 Ctrl+ ...
- 【九度OJ】题目1111:单词替换
题目1111:单词替换 题目描述: 输入一个字符串,以回车结束(字符串长度<=100).该字符串由若干个单词组成,单词之间用一个空格隔开,所有单词区分大小写.现需要将其中的某个单词替换成另一个单 ...
- Android最大可运行内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
- 通过uCGUIBulider4.0建立的ucGUI文件,控件汉字不能显示问题解决办法
由于uCGUIBulider4.0不能在64位操作系统中运行,于是在电脑上通过VMware Workstation Pro搭建虚拟的32位的win7环境,然后把win7中用uCGUIBulider4. ...
- 原生javaScript中使用Ajax实现异步通信
AJAX本质就是在HTTP协议的基础上以异步的方式与服务器进行通信,所谓异步,就是指某段程序执行时不会阻塞其它程序执行,其表现形式为程序的执行顺序不依赖程序本身的书写顺序,相反则为同步. 以下开始简单 ...
- Codeforces Zip-line 650D 345Div1D(LIS)
传送门 大意:给出一个序列,求修改一个数过后的最长上升子序列. 思路:可以用主席树在线搞,也可以用树状数组离线搞,明显后者好写得多.我们首先读取所有的询问,然后就把询问绑在给出的位置,然后我们正向做一 ...