2013-03-04 118 views
42

我有一个使用ASP.NET Web Api编写的系统的API,并且试图扩展它以允许上传图像。我已经做了一些搜索,并发现如何使用MultpartMemoryStreamProvider和一些异步方法接受文件的建议方式,但我在ReadAsMultipartAsync上的等待永远不会返回。Request.Content.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张贴到这一行动,命令如下:

C:\cURL>curl -i -F [email protected]:\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> 
+0

请问你的客户端代码是什么样子? – svick 2013-03-04 16:29:36

+0

你能分享你的原始请求的外观吗? – 2013-03-04 17:31:49

+0

在'await'之后的代码中放置一个断点。有时候,当你使用async/await(根据我的经验)时,它不会中断/下一行。 – Micah 2013-03-05 02:16:30

回答

81

我碰到类似的东西在.NET 4.0(没有异步/等待)。使用调试器的线程堆栈我可以知道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)) 
+3

出色的工作!我遇到了这个问题,并通过这个解决了问题。 – guogangj 2013-05-22 08:32:18

+0

谢谢,这工作真棒。 – bkorzynski 2013-07-22 21:07:25

+0

我正在使用Web API,我需要在构造函数中调用异步方法。这是我设法使其工作的唯一途径!谢谢! – 2013-07-23 15:19:40

4

随着another answer on stackoverflowa blog post about targetFramework的帮助下,我发现,在更新到4.5,并添加/更新下面的你web.config修复了这个问题:

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

工程像魔术。 – Dar 2017-10-25 09:05:53

0

我有一个工作.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; 
    } 
3

我遇到了与所有现代4.5.2框架相同的问题。

我的API方法接受使用多部分内容的POST请求上传的一个或多个文件。它对小文件运行良好,但对于大文件,我的方法只是永远吊死,因为ReadAsMultipartAsync()函数从未完成。

什么帮助了我:使用的ReadAsMultipartAsync()async控制器的方法和await来完成的,而不是获取任务结果的同步控制器的方法。

所以,这并不工作:

[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仅仅是一个同步功能以二进制内容,并产生一个字符串(可任何类型的处理)。

UPDATE最后我找到了解释在这篇文章中:https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

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

所以,基本上,“异步一路”方针背后都有一个原因,这就是一个很好的例子。

0

我也一样。我的解决方案

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