2013-07-22 44 views
5

Chúng tôi đang xây dựng một ứng dụng web đồng thời cao và gần đây chúng tôi đã bắt đầu sử dụng rộng rãi lập trình không đồng bộ (sử dụng TPL và async/await).HttpContent.ReadAsStringAsync khiến yêu cầu treo (hoặc các hành vi lạ khác)

Chúng tôi có một môi trường phân tán, trong đó các ứng dụng liên lạc với nhau thông qua các API REST (được xây dựng trên đầu trang của ASP.NET Web API). Trong một ứng dụng cụ thể, chúng tôi có một số DelegatingHandler sau khi gọi base.SendAsync (tức là sau khi tính toán phản hồi) ghi lại phản hồi cho tệp. Chúng tôi bao gồm thông tin của phản ứng cơ bản trong nhật ký (mã trạng thái, tiêu đề và nội dung):

public static string SerializeResponse(HttpResponseMessage response) 
{ 
    var builder = new StringBuilder(); 
    var content = ReadContentAsString(response.Content); 

    builder.AppendFormat("HTTP/{0} {1:d} {1}", response.Version.ToString(2), response.StatusCode); 
    builder.AppendLine(); 
    builder.Append(response.Headers); 

    if (!string.IsNullOrWhiteSpace(content)) 
    { 
     builder.Append(response.Content.Headers); 

     builder.AppendLine(); 
     builder.AppendLine(Beautified(content)); 
    } 

    return builder.ToString(); 
} 

private static string ReadContentAsString(HttpContent content) 
{ 
    return content == null ? null : content.ReadAsStringAsync().Result; 
} 

Vấn đề là thế này: khi mã đạt content.ReadAsStringAsync().Result dưới tải máy chủ nặng, yêu cầu đôi khi bị treo trên IIS. Khi nó xảy ra, nó đôi khi trả về một phản ứng - nhưng treo cứng trên IIS như thể nó không - hoặc trong những lúc khác nó không bao giờ trả về.

Tôi cũng đã thử đọc nội dung bằng cách sử dụng ReadAsByteArrayAsync và sau đó chuyển đổi thành String, không có may mắn.

Khi tôi chuyển đổi mã để sử dụng async khắp tôi nhận được kết quả thậm chí lạ:

public static async Task<string> SerializeResponseAsync(HttpResponseMessage response) 
{ 
    var builder = new StringBuilder(); 
    var content = await ReadContentAsStringAsync(response.Content); 

    builder.AppendFormat("HTTP/{0} {1:d} {1}", response.Version.ToString(2), response.StatusCode); 
    builder.AppendLine(); 
    builder.Append(response.Headers); 

    if (!string.IsNullOrWhiteSpace(content)) 
    { 
     builder.Append(response.Content.Headers); 

     builder.AppendLine(); 
     builder.AppendLine(Beautified(content)); 
    } 

    return builder.ToString(); 
} 

private static Task<string> ReadContentAsStringAsync(HttpContent content) 
{ 
    return content == null ? Task.FromResult<string>(null) : content.ReadAsStringAsync(); 
} 

Bây giờ HttpContext.Current là null sau khi cuộc gọi đến content.ReadAsStringAsync(), và nó giữ là null cho tất cả các yêu cầu tiếp theo! Tôi biết điều này nghe có vẻ không thể tin được - và nó đã cho tôi một thời gian và sự hiện diện của ba đồng nghiệp để chấp nhận rằng điều này đã thực sự xảy ra.

Đây có phải là hành vi mong đợi không? Tôi có làm gì sai ở đây không?

+2

Bạn nhận ra rằng gọi 'ReadContentAsStringAsync' và sau đó ngay lập tức kêu gọi' Result' trên nó là cơ bản phủ nhận sự không đồng bộ, đúng? Điều đó sẽ chặn cho đến khi công việc đã hoàn thành. Và 'HttpContext.Current' là' null' sau khi âm thanh chờ đợi âm thanh như nó chỉ không chảy qua 'chờ đợi' điểm, đó là irksome nhưng không * hoàn toàn * làm tôi ngạc nhiên. Bạn có thể lấy nó tại * start * của phương thức async của bạn và sau đó sử dụng biến cục bộ đó ... –

+0

Tôi có thể gọi quấn một lượt thử xung quanh một cuộc gọi đến response.EnsureSuccessStatusCode() trước khi thậm chí cố gắng xử lý nội dung. – cgotberg

+2

Bạn có chắc chắn rằng luôn có thể đọc được 'HttpResponseMessage.Content'? Dường như với tôi rằng cố gắng đọc luồng đầu ra của riêng bạn có thể không được hỗ trợ. –

Trả lời

0

Đối với vấn đề thứ hai của bạn, async/await là cú pháp đường cho trình biên dịch xây dựng một máy trạng thái mà cuộc gọi đến một hàm trước "await" trả về ngay lập tức trên luồng hiện tại ... một chứa HttpContext .Current trong lưu trữ cục bộ luồng của nó. Việc hoàn thành cuộc gọi không đồng bộ đó có thể xảy ra trên một chuỗi khác nhau ... một chuỗi không có HttpContext.Current trong lưu trữ cục bộ của luồng.

Nếu bạn muốn hoàn thành thực thi trên cùng một luồng (do đó có cùng các đối tượng trong lưu trữ cục bộ luồng như HttpContext.Current), thì bạn cần phải biết về hành vi này. Điều này đặc biệt quan trọng đối với các cuộc gọi từ luồng giao diện người dùng chính (nếu bạn đang xây dựng một ứng dụng Windows) hoặc trong ASP.NET, các cuộc gọi từ một chuỗi yêu cầu ASP.NET, nơi bạn phụ thuộc vào HttpContext.Current.

Xem tài liệu tham chiếu trên ConfigureAwait (sai). Ngoài ra, hãy xem một số hướng dẫn về Kênh 9 trên TPL. Một khi các công cụ "dễ" bị grokked, người trình bày sẽ luôn nói về vấn đề này vì nó gây ra các vấn đề tinh tế mà không dễ hiểu trừ khi bạn biết TPL đang làm gì bên dưới trang bìa.

Chúc may mắn.

Liên quan đến vấn đề đầu tiên của bạn, nếu người gọi nhận được kết quả, tôi không tin rằng IIS chưa hoàn thành yêu cầu. Làm thế nào bạn xác định rằng chuỗi yêu cầu ASP.NET được khởi xướng bởi người gọi này được treo trong IIS?

5

Tôi gặp sự cố này.Mặc dù, tôi đã không kiểm tra đầy đủ chưa, sử dụng CopyToAsync thay vì ReadAsStringAsync dường như để sửa chữa các vấn đề:

var ms = new MemoryStream(); 
await response.Content.CopyToAsync(ms); 
ms.Seek(0, SeekOrigin.Begin); 

var sr = new StreamReader(ms); 
responseContent = sr.ReadToEnd(); 
+0

nó cũng làm việc cho tôi! – Calvin