Jon*_*ill 54 c# c#-5.0 asp.net-web-api
我有一个使用ASP.NET Web Api编写的系统的API,我试图扩展它以允许上传图像.我已经做了一些谷歌搜索,并找到了如何使用MultpartMemoryStreamProvider和一些异步方法接受文件的推荐方法,但我在ReadAsMultipartAsync上等待永远不会返回.
这是代码:
[HttpPost]
public async Task<HttpResponseMessage> LowResImage(int id)
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var provider = new MultipartMemoryStreamProvider();
try
{
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var item in provider.Contents)
{
if (item.Headers.ContentDisposition.FileName != null)
{
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
Run Code Online (Sandbox Code Playgroud)
我可以一路走到:
await Request.Content.ReadAsMultipartAsync(provider);
Run Code Online (Sandbox Code Playgroud)
它永远不会完成.
我等待永远不会回来的原因是什么?
我试图使用curl POST这个动作,命令如下:
C:\cURL>curl -i -F filedata=@C:\LowResExample.jpg http://localhost:8000/Api/Photos/89/LowResImage
Run Code Online (Sandbox Code Playgroud)
我也尝试使用以下html来POST动作,同样的事情发生:
<form method="POST" action="http://localhost:8000/Api/Photos/89/LowResImage" enctype="multipart/form-data">
<input type="file" name="fileupload"/>
<input type="submit" name="submit"/>
</form>
Run Code Online (Sandbox Code Playgroud)
yee*_*uto 96
我在.NET 4.0中遇到了类似的东西(没有异步/等待).使用调试器的Thread堆栈,我可以看出ReadAsMultipartAsync正在将任务启动到同一个线程上,因此它会死锁.我做了这样的事情:
IEnumerable<HttpContent> parts = null;
Task.Factory
.StartNew(() => parts = Request.Content.ReadAsMultipartAsync().Result.Contents,
CancellationToken.None,
TaskCreationOptions.LongRunning, // guarantees separate thread
TaskScheduler.Default)
.Wait();
Run Code Online (Sandbox Code Playgroud)
TaskCreationOptions.LongRunning参数对我来说很关键,因为没有它,调用将继续将任务启动到同一个线程上.您可以尝试使用类似下面的伪代码来查看它是否适用于C#5.0:
await TaskEx.Run(async() => await Request.Content.ReadAsMultipartAsync(provider))
Run Code Online (Sandbox Code Playgroud)
我遇到了所有现代4.5.2框架的同样问题.
我的API方法接受使用带有多部分内容的POST请求上传的一个或多个文件.它适用于小文件,但是对于大文件,我的方法只是永远挂起,因为ReadAsMultipartAsync()
函数从未完成.
什么帮助我:使用async
控制器的方法,并await
为ReadAsMultipartAsync()
完成,而不只是简单的同步控制器方法任务结果.
所以,这不起作用:
[HttpPost]
public IHttpActionResult PostFiles()
{
return Ok
(
Request.Content.ReadAsMultipartAsync().Result
.Contents
.Select(content => ProcessSingleContent(content))
);
}
private string ProcessSingleContent(HttpContent content)
{
return SomeLogic(content.ReadAsByteArrayAsync().Result);
}
Run Code Online (Sandbox Code Playgroud)
而这工作:
[HttpPost]
public async Task<IHttpActionResult> PostFiles()
{
return Ok
(
await Task.WhenAll
(
(await Request.Content.ReadAsMultipartAsync())
.Contents
.Select(async content => await ProcessSingleContentAsync(content))
)
);
}
private async Task<string> ProcessSingleContentAsync(HttpContent content)
{
return SomeLogic(await content.ReadAsByteArrayAsync());
}
Run Code Online (Sandbox Code Playgroud)
其中SomeLogic
只是一个同步函数,它采用二进制内容并生成一个字符串(可以是任何类型的处理).
更新最后我在本文中找到了解释:https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
此死锁的根本原因是await处理上下文的方式.默认情况下,当等待未完成的任务时,捕获当前的"上下文"并用于在任务完成时恢复该方法.这个"上下文"是当前的SynchronizationContext,除非它是null,在这种情况下它是当前的TaskScheduler.GUI和ASP.NET应用程序具有SynchronizationContext,一次只允许运行一个代码块.当await完成时,它会尝试在捕获的上下文中执行异步方法的剩余部分.但是该上下文已经有一个线程,它(同步)等待异步方法完成.他们每个人都在等着对方,造成僵局.
所以,基本上,"异步一路"指南背后有一个原因,这是一个很好的例子.