Request.Content.ReadAsMultipartAsync永远不会返回

我有一个使用ASP.NET Web Api编写的系统的API,并试图扩展它以允许上传图像。 我做了一些Googlesearch,发现如何使用MultpartMemoryStreamProvider和一些asynchronous方法来接受文件的build议方式,但是我在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); } } 

我可以一路走过来:

 await Request.Content.ReadAsMultipartAsync(provider); 

在这一点上,它永远不会完成。

为什么我永远不会回来的原因是什么?

更新

我正在尝试使用curl来POST这个动作,命令如下:

 C:\cURL>curl -i -F filedata=@C:\LowResExample.jpg http://localhost:8000/Api/Photos/89/LowResImage 

我也尝试使用下面的HTML来发布到行动以及相同的事情发生:

 <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> 

我遇到了类似的.NET 4.0(没有asynchronous/等待)。 使用debugging器的线程堆栈我可以告诉ReadAsMultipartAsync启动任务到同一个线程,所以它会死锁。 我做了这样的事情:

 IEnumerable<HttpContent> parts = null; Task.Factory .StartNew(() => parts = Request.Content.ReadAsMultipartAsync().Result.Contents, CancellationToken.None, TaskCreationOptions.LongRunning, // guarantees separate thread TaskScheduler.Default) .Wait(); 

TaskCreationOptions.LongRunning参数对我来说是关键的,因为没有它,调用将继续将任务启动到同一个线程上。 您可以尝试使用类似下面的伪代码来查看它是否适合您在C#5.0中使用:

 await TaskEx.Run(async() => await Request.Content.ReadAsMultipartAsync(provider)) 

在stackoverflow和关于targetFramework的博客文章的另一个答案的帮助下,我发现更新到4.5,并在您的web.config中添加/更新以下内容修复此问题:

 <system.web> <compilation debug="true" targetFramework="4.5"/> </system.web> <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> </appSettings> 

我遇到了所有现代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); } 

这工作:

 [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()); } 

SomeLogic只是一个采用二进制内容并产生一个string(可以是任何types的处理)的同步函数。

更新最后我find了这篇文章中的解释: https : //msdn.microsoft.com/en-us/magazine/jj991977.aspx

这种死锁的根本原因是由于等待处理上下文的方式。 默认情况下,当一个不完整的任务被等待时,当前的“上下文”被捕获并用于在任务完成时恢复该方法。 这个“上下文”是当前的SynchronizationContext,除非它是空的,在这种情况下,它是当前的TaskScheduler。 GUI和ASP.NET应用程序有一个SynchronizationContext,一次只允许运行一个代码块。 等待完成时,它会尝试在捕获的上下文中执行asynchronous方法的其余部分。 但是,上下文已经有一个线程,它正在(同步)等待asynchronous方法完成。 他们每个人都在等待另一个,导致僵局。

所以,基本上, “一路走来”的原则背后有一个原因,这就是一个很好的例子。

我有一个工作.net MVC WebAPi项目与以下Post方法似乎工作得很好。 这与你已经很相似,所以这应该是有帮助的。

  [System.Web.Http.AcceptVerbs("Post")] [System.Web.Http.HttpPost] public Task<HttpResponseMessage> Post() { // Check if the request contains multipart/form-data. if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } string fileSaveLocation = @"c:\SaveYourFile\Here\XXX"; CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(fileSaveLocation); Task<HttpResponseMessage> task = Request.Content.ReadAsMultipartAsync(provider).ContinueWith<HttpResponseMessage>(t => { if (t.IsFaulted || t.IsCanceled) { Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception); } foreach (MultipartFileData file in provider.FileData) { //Do Work Here } return Request.CreateResponse(HttpStatusCode.OK); } ); return task; } 

我也一样。 我的解决scheme

 public List<string> UploadFiles(HttpFileCollection fileCollection) { var uploadsDirectoryPath = HttpContext.Current.Server.MapPath("~/Uploads"); if (!Directory.Exists(uploadsDirectoryPath)) Directory.CreateDirectory(uploadsDirectoryPath); var filePaths = new List<string>(); for (var index = 0; index < fileCollection.Count; index++) { var path = Path.Combine(uploadsDirectoryPath, Guid.NewGuid().ToString()); fileCollection[index].SaveAs(path); filePaths.Add(path); } return filePaths; } 

并调用

 if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var filePaths = _formsService.UploadFiles(HttpContext.Current.Request.Files);