异步陷阱之IO篇
很多教程和资料都强调流畅的用户体验需要异步来辅助,核心思想就是保证用户前端的交互永远有最高的优先级,让一切费时的逻辑通通放到后台,等到诸事完备,通知一下前端给个提示或者继续下一步。随着.NET发展,async和await关键字的推广,Task Parallel Library (TPL)的稳步发展, 异步编程也越来越多的被重视和采用,很多时候非常便利的解决各种性能问题,但同时也带来了很多的陷阱。
这里我抛出一个实际项目中遇到的陷阱,先简单交代一下故事背景:SpreadJS产品有一个Excel IO部件,是一个ASP.NET MVC Web API(MVC4)应用,用来导入Excel文件到SpreadJS中;其工作过程是客户端先上传Excel文件,服务器端接收文件后读出内容,以SpreadJS特有的JSON格式回传给客户端。很长一段时间工作正常,直到某一天有一个“大神”级的客户反馈他在使用Excel IO过程中会一定几率随机出现导入失败,具体的表现是在返回的JSON数据中提示有IO错误,好吧,附上用户场景的代码片段(略去了脚本引用,DOM以及其他机密代码):
$(document).ready(function() {
// initialize 10 spreadjs widgets
for(var i = 0; i < 10; i++) {
$("#ss_" + i).wijspread({ sheetCount: 2 });
}
// import handler
$("#importButton").click(function() {
for(var i = 0; i < 10; i++) {
importToSpread("ss" + i);
}
});
// import process
function importToSpread(target) {
var formData = new FormData();
formData.append("file", $("#importingExcelFile").get(0).files[0]);
formData.append("ExcelOpenFlags", "NoFlagsSet");
formData.append("TextFileOpenFlags", "None");
formData.append("Password", "");
$.ajax( {
url: "http://your.excelio.path/xsapi/import",
type: "POST",
success: function(data, textStatus, jqXHR) {
$("#" + target).wijspread("spread").fromJSON(JSON.parse(jqXHR.responseText).spread);
},
data: formData,
contentType: false,
processData: false,
headers: { "Accept": "application/json" }
});
}
});
也许各位看官可能有话说了:这明显的穷折腾么,有这么把一个文件重复导入10次的实际场景吗?嗯,这是一个社会工程学问题,略过,呵呵。
根据用户的代码,可以分析得到一些关键信息:
1、用户在很短时间内快速提交了多个请求并上传文件;
2、返回结果会随机出现IO错误;
由此可以得出结论:应该是服务器处理上传的Excel文件时,某个文件在特定情况下不可用,从而导致处理程序抛出IO异常。什么情况会导致IO不可用呢?似乎一下子还真无从下手,作为开发人员,最容易想到的方法就是祭出IDE,直接挂上调试器,只要捕获到这个IO异常就好了。经过几次尝试,终于看到了IO异常了,如下图:

看来前面的分析是对的,文件在特定 情况不可用,但是为什么不可用呢?从上面的IO异常信息可以看出,这个文件是ASP.NET临时保存的上传文件。在ASP.NET WEB API中,处理上传文件的思路和方法如下:
var root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try {
await Request.Content.ReadAsMultipartAsync(provider);
} catch (Exception ex) {
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
var file = provider.FileData.FirstOrDefault();
// File.OpenRead(file.LocalFileName) // may get exception here
从这个片段很容易分析出一下两种可能导致文件IO的情况:
1、文件的LocalFileName不唯一
2、读取上传内容的异步操作结束但是文件还没有释放
显然,第一条可以排除,因为异常信息里可以看到文件的名字有一个GUID,基本可以保证绝对唯一,所以,问题肯定发生在这里的异步处理。
为了深入的搞清楚发生了什么,我查看了ReadAsMultipartAsync的源代码,这里面会调用MultipartFormDataStreamProvider上的GetStream方法来处理上传的文件:
// ... 略去参数处理
string localFileName = this.GetLocalFileName(headers);
str = Path.Combine(this._rootPath, Path.GetFileName(localFileName));
// ... 略去部分无关逻辑
MultipartFileData item = new MultipartFileData(headers, str);
this._fileData.Add(item);
return File.Create(str, this._bufferSize, FileOptions.Asynchronous);
这里调用GetLocalFileName来获取临时文件名,很清楚的使用了Guid.NewGuid()来保证文件名永远不会重复;焦点转到最后一句返回一个可写的FileStream,注意这里的第三个参数是FileOptions.Asynchronous,就是说,这个FileStream实际是异步IO,但是内部处理逻辑没有等待这个结果就直接走后续的逻辑了,这样导致在服务器运行在高IO并发的情况就很容易发生IO异常。
以上分析了问题,但如何解决呢(某PM话外音:那谁谁,快点啊,客户催着呢),很简单,去除调这个异步IO就可以了,好吧,代码一点也不简单,重写这个GetStream方法,保证获取的FileStream使用同步,虽然一定程度降低了性能,但好歹能解决问题。
参考示例工程代码:下载地址
更新补充:在ASP.NET MVC 5中重写了ReadAsMultipartAsync所在的整个类,已经修复了这个问题(至少我试过同时1000次毫无压力),参考示例中AsyncIoTrap_v5工程。
备注:昨天在OSChina上推出了Wijmo 5 《jQuery UI 组件集 Wijmo 五年最大更新,Mobile First!》。但是本次发布的Wijmo 5 Beta版本未包含SpreadJs。
异步陷阱之IO篇的更多相关文章
- 异步陷阱之IO
异步陷阱之IO篇 很多教程和资料都强调流畅的用户体验需要异步来辅助,核心思想就是保证用户前端的交互永远有最高的优先级,让一切费时的逻辑通通放到后台,等到诸事完备,通知一下前端给个提示或者继续下一步.随 ...
- 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】
下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...
- Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程
1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...
- 异步非阻塞IO的Python Web框架--Tornado
Tornado的全称是Torado Web Server,从名字上就可知它可用作Web服务器,但同时它也是一个Python Web的开发框架.最初是在FriendFeed公司的网站上使用,FaceBo ...
- Android 异步消息处理机制终结篇 :深入理解 Looper、Handler、Message、MessageQueue四者关系
版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.概述 我们知道更新UI操作我们需要在UI线程中操作,如果在子线程中更新UI会发生异常可能导致崩溃,但是在UI线程中进行耗时操作又会导致ANR,这 ...
- NodeJS示例异步式(Asynchronous)IO与同步式Synchronous)IO
理解IO IO(Input/Output)通常是指计算机线程进行慈磁盘读写或者网络通信时的一种行为. 同步式(Synchronous)IO和异步式(Asynchronous )IO ...
- C# 基础知识系列- 14 IO篇 文件的操作
0. 前言 本章节是IO篇的第二集,我们在上一篇中介绍了C#中IO的基本概念和一些基本方法,接下来我们介绍一下操作文件的方法.在编程的世界中,操作文件是一个很重要的技能. 1. 文件.目录和路径 在开 ...
- C# 基础知识系列- 14 IO篇 文件的操作 (3)
本篇继续前两篇内容,跟大家介绍一下Path类以及FileSystemInfo这个类的主要方法和属性. 上文提到,在<C# 基础知识系列-IO篇>之文件相关的内容完结之后,会带领大家开发一个 ...
- 漫谈linux之文件IO篇(SSD写性能和机械硬盘差不多,读是4到10倍)
前同事的文章,觉得写得很清晰,收藏了. http://blog.chinaunix.net/uid-27105712-id-3270102.html 在Linux 开发中,有几个关系到性能的东西,技术 ...
随机推荐
- node.js使用redis储存session(详细步骤)
转储session的原因 网上有许多session需要用数据库储存的原因,对我来说原因很简单,仅仅只是node的生产环境不允许将session存到服务器的内存中.会报一个内存溢出的风险警告.所以我决定 ...
- 在jsp的js和css里面使用EL表达式取值|style里面用$取值
众所周知,如果直接在jsp的js或者css语句块里面写${***}取值的话,程序会不识别这玩意,但是,我们有时候确实需要动态取值,比如,js为了获得对象的某一个值,不方便用js的getElementB ...
- 你不知道的javascript读书笔记3
概述 这是我看<你不知道的JavaScript(中卷)>中关于类型检查的笔记,供以后开发时参考,相信对其他人也有用. typeof 我们知道js中有七种内置类型:undefined, nu ...
- location匹配
=/ 表示精确匹配 www.sensetime.com/ ~ :表示做正则表达式匹配,区分字符大小写 ~* : 表示做正则表达式匹配,不区分大小写 ^~: URI的左半部分匹配,不区分大小写 匹配优 ...
- rest framework 尝鲜
安装 pip install djangorestframework 新建项目 python manage.py startapp idcs 添加模型(models.py) class Idcs(mo ...
- 【BJOI2019】排兵布阵 DP
题目大意:有$n$座城堡,$s$轮游戏. 对于第$x$轮,第i座城堡的士兵数量为$a[x][i]$. 如果你需要攻下第i座城堡,你在第i座城堡部署的士兵必须严格大于$2a[x][i]$,如果攻下了你会 ...
- 使用Chrome开发者工具调试Android端内网页(微信,QQ,UC,App内嵌页等)
使用Chrome开发者工具调试Android端内网页(微信,QQ,UC,App内嵌页等) 前言 移动端页面调试一直是好多朋友头疼的问题,iOS 由于其封闭的特性和整体较高的性能,整体适配相对好做,调试 ...
- Java RMI 概观
RMI是Java的一组拥护开发分布式应用程序的API. RMI使用Java语言接口定义了远程对象,它集合了Java序列化和Java远程方法协议(Java Remote Method Protocol) ...
- ubuntu18.04 出现 Command 'ifconfig' not found 问题的解决办法
我们在虚拟主机中查看ip地址需要输入ifconfig,但是报以下错误: 系统提示我们安装 net-tools,当我们输入以下命令,即可安装完成. sudo apt-get install net-to ...
- welcome-file-list修改后不生效
用别的浏览器重新尝试一下,或者清缓存.我就是这样解决的.值得注意的就是,<welcome-file>里面指定的文件可以是.do或者是action.