【发布时间】:2015-04-10 22:23:23
【问题描述】:
今天的问题是,当使用 WebApi 2 和基于 Async ApiController 的 Get 方法时,它会返回文件的内容。当我将 Get 方法更改为同步时,它工作得很好,但是一旦我将它转换回异步,它就会过早地关闭流。 (提琴手报告连接被中止)工作同步代码是:
public void Get(int id)
{
try
{
FileInfo fileInfo = logic.GetFileInfoSync(id);
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.ClearContent();
response.Buffer = true;
response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileInfo.Node.Name + fileInfo.Ext + "\"");
response.AddHeader("Content-Length", fileInfo.SizeInBytes.ToString());
response.ContentType = "application/octet-stream";
logic.GetDownloadStreamSync(id, response.OutputStream);
response.StatusCode = (int)HttpStatusCode.OK;
//HttpContext.Current.ApplicationInstance.CompleteRequest();
response.End();
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
而GetDownloadStreamSync如下:
public async Task GetDownloadStream(string fileIdentifier, Stream streamToCopyTo)
{
string filePath = Path.Combine(fileIdentifierFolder, fileIdentifier);
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, false))
{
fs.CopyTo(streamToCopyTo);
}
}
--------异步代码----------
异步版本完全相同,除了:
public async Task Get(int id)
{
FileInfo fileInfo = await logic.GetFileInfoSync(id); // database opp
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.ClearContent();
response.Buffer = true;
response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileInfo.Node.Name + fileInfo.Ext + "\"");
response.AddHeader("Content-Length", fileInfo.SizeInBytes.ToString());
response.ContentType = "application/octet-stream";
await logic.GetDownloadStreamSync(id, response.OutputStream);
//database opp + file I/O
response.StatusCode = (int)HttpStatusCode.OK;
//HttpContext.Current.ApplicationInstance.CompleteRequest();
response.End();
}
GetDownloadStream 的异步实现如下:(streamToCopyTo 是来自 response.OutputStream 的 OutputStream)
public async Task GetDownloadStream(string fileIdentifier, Stream streamToCopyTo)
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, true))
{
await fs.CopyToAsync(streamToCopyTo);
}
}
我们正在尝试从头到尾采用 async/await 模式,所以希望有人知道为什么这会失败?我也尝试过不调用 Response.End()、Response.Flush() 和 HttpContext.Current.ApplicationInstance.CompleteRequest()。此外,为了响应下面的问题/cmets,我在 response.End() 上放置了一个断点,结果它没有被 GetDownloadStream 方法命中。也许 OutputStream 不是异步的?欢迎任何想法! 谢谢
****************************** 最终解决方案 ******************* ********
非常感谢所有发表评论的人,尤其感谢 @Noseratio 对 FileOptions.DeleteOnClose 的建议。
[HttpGet]
public async Task<HttpResponseMessage> Get(long id)
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
Node node = await logic.GetFileInfoForNodeAsync(id);
result.Content = new StreamContent(await logic.GetDownloadStreamAsync(id));
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = node.Name + node.FileInfo.Extension
};
result.Content.Headers.ContentLength = node.FileInfo.SizeInBytes;
return result
}
GetDownloadStreamAsync 看起来像这样:
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, FileOptions.DeleteOnClose | FileOptions.Asynchronous);
我忽略了我也在动态解密文件流,这确实有效,所以对于那些感兴趣的人......
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, FileOptions.DeleteOnClose | FileOptions.Asynchronous);
RijndaelManaged rm = new RijndaelManaged();
return new CryptoStream(fs, GetDecryptor(rm, password), CryptoStreamMode.Read);
【问题讨论】:
-
MSDN 的解释是:“await 表达式不会阻塞它正在执行的线程。相反,它会导致编译器注册 async 方法的其余部分作为对等待的任务。然后控制权返回到异步方法的调用者。当任务完成时,它调用它的继续,并且异步方法的执行从它停止的地方继续。等待表达式只能出现在立即封闭的方法的主体中、lambda 表达式或由 async 修饰符标记的匿名方法。在其他地方,它被解释为标识符。"
-
异步情况下方法的签名是什么? (我假设
Task Get...,但最好确定一下)。 -
如果您在问题中遗漏了什么,您可以发布完整的异步版本吗?
-
我已经编辑了额外的代码。
-
为什么一定要自己指定内容长度?你必须做这么多工作才能做这样简单的事情,这似乎很奇怪。你刚刚复制的流会发生什么?您的异步内容我们听起来。
标签: c# asp.net-mvc asp.net-web-api async-await asp.net-web-api2