返回该系列目录《基于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的更多相关文章

  1. Coursera 机器学习课程 机器学习基础:案例研究 证书

    完成了课程1  机器学习基础:案例研究 贴个证书,继续努力完成后续的课程:

  2. Top100Summit全球案例研究峰会第一天总结——云计算和大数据

    很荣幸受邀参加Top100Summit全球软件案例研究峰会,这次的大会主题是<技术推动商业变革>,组委会从全国投稿的460多件案例中甄选出100件具有代表价值的案例,进行为期4天的分享,第 ...

  3. JavaEE Tutorials (30) - Duke综合案例研究示例

    30.1Duke综合应用的设计和架构456 30.1.1events工程458 30.1.2entities工程459 30.1.3dukes—payment工程461 30.1.4dukes—res ...

  4. JavaEE Tutorials (29) - Duke辅导案例研究示例

    29.1Duke辅导应用的设计和架构44529.2主界面447 29.2.1主界面中使用的Java持久化API实体447 29.2.2主界面中使用的企业bean448 29.2.3主界面中使用的Web ...

  5. JavaEE Tutorials (28) - Duke书店案例研究示例

    28.1Duke书店的设计和架构43828.2Duke书店接口439 28.2.1Book Java持久化API实体439 28.2.2Duke书店中使用的企业bean440 28.2.3Duke书店 ...

  6. 案例研究:Web应用出现间歇性的SqlException

    案例研究:Web应用出现间歇性的SqlException 2013-07-29 14:36 by 微软互联网开发支持, 231 阅读, 3 评论, 收藏, 编辑 最近有客户找到我,说他们生产环境的事件 ...

  7. Netty实战十四之案例研究(一)

    1.Droplr——构建移动服务 Bruno de Carvalho,首席架构师 在Droplr,我们在我的基础设施的核心部分.从我们的API服务器到辅助服务的各个部分都使用了Netty. 这是一个关 ...

  8. 《Java大学教程》—第11章 案例研究--第1部分

    自测题:1.    图11-1的UML设计中各个类之间的关系.Hostel与TenantList是关联关系:TenantList和PaymentList与ObjectList是泛化关系.TenantL ...

  9. UML和模式应用3:迭代和进化式分析和设计案例研究

    1.前言 如何进行迭代和进化式分析和设计?将采用案例研究的方式贯穿始终.案例研究所包含的内容: UI元素 核心应用逻辑层 数据库访问 与外部软硬构件的协作 本章关于OOA/D主要介绍核心应用逻辑层 2 ...

  10. 洞察行业领先者的前沿思想——第五届TOP100全球软件案例研究峰会精彩谢幕

    (第五届TOP100summit开幕式现场) 12月09日-12日,由msup主办的第五届TOP100全球软件案例研究峰会(以下简称TOP100summit)在北京国家会议中心举行,作为互联网行业最有 ...

随机推荐

  1. 大前端学习笔记整理【一】CSS盒模型与基于盒模型的6种元素居中方案

    概览 CSS盒模型,规定了元素框来处理元素的 内容.内边距.边框和外边距的方式 元素部分是指内容部分,也是最实际的内容,包围内容的称之为内边距,内边距外围是边框,边框外围就是外边距:且外边距是透明的, ...

  2. Android照片墙加强版,使用ViewPager实现画廊效果

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/12646775 记得关于照片墙的文章我已经写过好几篇了,有最基本的照片墙,有瀑布流模 ...

  3. Stack Overflow is a question and answer site

    http://stackoverflow.com/ _ Stack Overflow is a question and answer site for professional and enthus ...

  4. CentOS 7下MySQL安装配置

    安装: #centos 7 yum install -y mariadb mariadb-server mariadb-devel #debian 8 apt-get install nginx ma ...

  5. [LeetCode]413 Arithmetic Slices

    A sequence of number is called arithmetic if it consists of at least three elements and if the diffe ...

  6. HP iLo2 试用序列号

    HP iLo2 试用序列号 2 条回复 32Q8W-GKHTR-NPDKY-5CD79-T525H hp的ilo2功能实在太有用了,不用往那个恶劣的机房跑了,系统重装也直接远程完成. 这个试用序列号用 ...

  7. 前端学习之本地储存与cookie

    今天主要的学习内容是cookie与本地储存的知识, 在HTML5中,本地存储是一个window的属性,包括localStorage和sessionStorage,从名字应该可以很清楚的辨认二者的区别, ...

  8. Web测试基础点

    摘自:软件工程师成长之路 1.获取当前使用的Flash播放器版本(Flash Player Version)信息 在浏览器地址栏输入http://www.playerversion.com/,即可显示

  9. JS动态级联菜单

    JS动态级联菜单是前端常用的一个功能,特此抽时间研究了下,附上代码 <html> <head> <meta charset="utf-8" /> ...

  10. c++中的指针之指针在数组

    使用一维指针数组输出一维数组中的数 int array[]={1,2,3,4,5,6};        int *p; p=array;        for(int i=0;i<6;i++){ ...