8

今天的問題是,當使用WebApi 2和基於異步ApiController的Get方法時,它返回文件的內容。當我將Get方法更改爲同步時,它工作得很好,但只要我將其轉換回異步,它就會過早關閉流。 (提琴手報告連接已中止)工作同步代碼:C#異步ApiController過早關閉OutputStream

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

隨着異步執行GetDo的wnloadStream如下:(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); 
    } 
} 

我們正試圖從正面擁抱異步/ AWAIT模式回來,所以希望有人意識到爲什麼會失敗?我也嘗試不調用Response.End(),Response.Flush()和HttpContext.Current.ApplicationInstance.CompleteRequest()。另外,爲了迴應下面的問題/評論,我在響應中放置了一個斷點.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); 
+0

這裏是MSDN的解釋是:「一個等待表達式不會阻止它在其上執行線程相反,它會導致編譯器將異步方法的其餘部分註冊爲待處理任務的延續,然後控制權返回給異步方法的調用者,當任務完成時,它調用其繼續,並且異步方法的執行從其停止的地方恢復。 只有在直接使用封閉方法,lambda表達式或由異步修飾符標記的匿名方法的主體中才會出現等待表達式。在其他地方,它將被解釋爲標識符。「 – user1789573 2015-02-10 18:44:22

+0

什麼是異步中方法的簽名案件? (我假設「Task Get ...」,但最好確定)。 – 2015-02-10 18:56:30

+0

如果您錯過了您的問題中的某些內容,您可以發佈完整的異步版本嗎? – EZI 2015-02-10 18:57:29

回答

1

它會採取有一個完整的攝製情況回答您的具體問題,但我不認爲你需要async/await在這裏所有。我也認爲應儘可能避免直接使用HttpContext.Current.Response,尤其是在異步WebAPI控制器方法中。

在這種特殊情況下,你可以使用HttpResponseMessage

[HttpGet] 
public HttpResponseMessage Get(int id) 
{ 
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); 
    FileInfo fileInfo = logic.GetFileInfoSync(id); 

    FileStream fs = new FileStream(
     filePath, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, false); 

    result.Content = new StreamContent(fs); 
    result.Content.Headers.ContentType = 
     new MediaTypeHeaderValue("application/octet-stream"); 
    result.Content.Headers.ContentDisposition = 
     new ContentDispositionHeaderValue("attachment") 
     { 
      FileName = fileInfo.Node.Name + fileInfo.Ext 
     }; 
    result.Content.Headers.ContentLength = fileInfo.SizeInBytes; 

    return result; 
} 

這裏沒有明確的不同步,因此該方法不async。然而,如果你仍然需要引入一些await,方法想是這樣的:

[HttpGet] 
public async Task<HttpResponseMessage> Get(int id) 
{ 
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); 
    // ... 
    await fs.CopyToAsync(streamToCopyTo) 
    // ... 
    return result; 
} 
+0

有關第一種解決方案的問題,它是否使FileStream處於打開狀態?我上面的解決方案在它周圍有一個很好的使用聲明,以確保它被關閉並正確處置,而在第一個解決方案中,我略微擔心FileStream被打開,這是真的嗎?我試圖在使用中包裝FileStream並在文件下載完成後將文件從本地驅動器中刪除,如果這樣做更有意義? – TChadwick 2015-02-12 19:26:18

+1

@TChadwick,不會保持打開狀態,WebAPI運行庫一旦完成就會在流上調用「Close」。如果您需要刪除它,只需使用FileOptions.DeleteOnClose | FileOptions.Asynchronous'作爲'new FileStream'的最後一個參數。 – Noseratio 2015-02-12 21:24:15

+0

@TChadwick,所以你有這個方法工作,最終? – Noseratio 2015-02-19 23:37:57

1

問題的根源實際在於使用Response.End()。當你運行Async時,它將在流文件內容完成之前執行Response.End()。這在使用Sync版本時沒有看到,因爲Response.End()在完成流式傳輸文件內容之後纔會被調用。

Response.End()是一個特別糟糕的方式來說你正在處理,因爲它拋出一個TreadAbortException。相反,你應該使用HttpContext.Current.ApplicationInstance.CompleteRequest()

請參見本文的詳細信息Response.End, Response.Close, and How Customer Feedback Helps Us Improve MSDN Documentation

+0

謝謝你的迴應馬修。有或沒有Response.End(),問題是一樣的,我也嘗試了CompleteRequest()。 – TChadwick 2015-02-10 21:25:26

+0

@TChadwick,這是我最近編寫的內容幫助程序,它幫助通過HTTP響應將文件流式傳輸到客戶端。 http://pastebin.com/5B29Pgdp它不是異步的,但它應該在異步上下文中工作。試試看看它是否有效。 – 2015-02-10 22:54:26

+0

再次感謝您的回覆,我已經深入瞭解了這一點,並且當我在Sync中運行這些相同的調用時,它們工作正常。我發現調用CompleteRequest()實際上將響應代碼更改爲204(無內容)而不是200,導致瀏覽器不下載該文件,並且我不得不調用Response.End()以使其返回200代碼並且有它的工作。我想擁抱閱讀異步文件並將其寫入到異步的OutputStream中,但在這一點上似乎是不現實的...... – TChadwick 2015-02-10 23:12:47