2013-03-04 176 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);