Cuộc gọi không đồng bộ với sự chờ đợi trong HttpClient không bao giờ trở lại


95

Tôi có một cuộc gọi mà tôi đang thực hiện từ bên trong một C#ứng dụng metro dựa trên xaml trên Win8 CP; lệnh gọi này chỉ cần truy cập vào một dịch vụ web và trả về dữ liệu JSON.

HttpMessageHandler handler = new HttpClientHandler();

HttpClient httpClient = new HttpClient(handler);
httpClient.BaseAddress = new Uri("http://192.168.1.101/api/");

var result = await httpClient.GetStreamAsync("weeklyplan");
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(WeeklyPlanData[]));
return (WeeklyPlanData[])ser.ReadObject(result);

Nó bị treo awaitnhưng cuộc gọi http thực sự trả về gần như ngay lập tức (xác nhận thông qua fiddler); nó như thể awaitbị bỏ qua và nó chỉ treo ở đó.

Trước khi bạn hỏi - CÓ - khả năng Mạng Riêng được bật.

Bất kỳ ý tưởng tại sao điều này sẽ bị treo?


1
Bạn đang gọi asyncphương pháp đó như thế nào? Nó không ném ra một ngoại lệ?
svick

Câu trả lời:


136

Kiểm tra câu trả lời này cho câu hỏi của tôi có vẻ rất giống nhau.

Một cái gì đó để thử: gọi ConfigureAwait(false)Nhiệm vụ được trả về GetStreamAsync(). Ví dụ

var result = await httpClient.GetStreamAsync("weeklyplan")
                             .ConfigureAwait(continueOnCapturedContext:false);

Điều này có hữu ích hay không tùy thuộc vào cách mã của bạn ở trên đang được gọi - trong trường hợp của tôi, việc gọi asyncphương thức bằng cách sử dụng Task.GetAwaiter().GetResult()khiến mã bị treo.

Điều này là do GetResult()chặn luồng hiện tại cho đến khi Tác vụ hoàn thành. Khi nhiệm vụ hoàn thành, nó cố gắng nhập lại ngữ cảnh luồng mà nó đã được bắt đầu nhưng không thể vì đã có một luồng trong ngữ cảnh đó, bị chặn bởi lệnh gọi GetResult()... deadlock!

Bài đăng MSDN này đi sâu vào một chút chi tiết về cách .NET đồng bộ hóa các luồng song song - và câu trả lời cho câu hỏi của riêng tôi đưa ra một số phương pháp hay nhất.


12
Cảm ơn, gần như đã từ bỏ async / await trước khi thấy điều này.
Den

4
Tôi cũng vậy! Tại sao công cụ này không được ghi chép tốt hơn? Cảm ơn một lần nữa
Avrohom Yisroel

1
Nó sẽ xảy ra nếu nó không nằm trên ngữ cảnh UI và ngữ cảnh ASP.NET?
machinarium

1
Câu trả lời tuyệt vời! Nhưng tôi bối rối tại sao cho đến nay tôi chỉ gặp sự cố này khi sử dụng HttpClient, có vẻ như việc triển khai cơ bản bên trong HttpClient không được triển khai chính xác. Các cách giải quyết khác mà tôi đã tìm thấy liên quan đến việc đặt luồng hiện tại là STA, điều này giúp ích nhưng thực sự gián tiếp, đặc biệt là khi bạn đang sử dụng lắp ráp của bên thứ 3 và không biết rằng bên dưới một số cuộc gọi chắc chắn sẽ chờ phản hồi rằng nó sẽ không bao giờ nhận được. Trong trường hợp của tôi, dll ở trong nhà, vì vậy chúng tôi có thể Cấu hình chờ đợi ... nhưng nó phải được thực hiện ở mức thấp nhất đối với HttpClient obj.
Chris Schaller

2
@ChrisSchaller Đảm bảo đọc câu trả lời đầy đủ tại stackoverflow.com/a/10351400/174735 , giải thích khá đầy đủ về vấn đề này.
Benjamin Fox,

5

Chỉ cần lưu ý - nếu bạn bỏ lỡ thời gian chờ đợi ở cấp cao nhất trong bộ điều khiển ASP.NET và bạn trả lại tác vụ thay vì kết quả dưới dạng phản hồi, nó thực sự chỉ bị treo trong (các) lệnh gọi chờ đợi lồng nhau mà không có lỗi. Một sai lầm ngớ ngẩn, nhưng nếu tôi xem bài đăng này, nó có thể đã giúp tôi tiết kiệm thời gian kiểm tra mã xem có điều gì đó kỳ quặc không.


0

Tuyên bố từ chối trách nhiệm: Tôi không thích giải pháp ConfigureAwait () vì tôi thấy nó không trực quan và khó nhớ. Thay vào đó, tôi đã đi đến kết luận để gói các cuộc gọi phương thức không chờ đợi trong Task.Run (() => myAsyncMethodNotUsingAwait ()). Điều này dường như hoạt động 100% nhưng có thể chỉ là một điều kiện của cuộc đua !? Tôi không chắc những gì đang diễn ra là trung thực. Kết luận này có thể sai và tôi mạo hiểm điểm StackOverflow của mình ở đây để hy vọng học hỏi từ các nhận xét :-P. Hãy đọc chúng!

Tôi vừa gặp sự cố như mô tả và tìm thêm thông tin ở đây .

Câu lệnh là: "bạn không thể gọi một phương thức không đồng bộ"

await asyncmethod2()

từ một phương pháp chặn

myAsyncMethod().Result

Trong trường hợp của tôi, tôi không thể thay đổi phương thức gọi và nó không phải là không đồng bộ. Nhưng tôi không thực sự quan tâm đến kết quả. Như tôi nhớ, nó cũng không hoạt động khi xóa .Result và thiếu phần chờ đợi.

Vì vậy, tôi đã làm điều này:

public void Configure()
{
    var data = "my data";
    Task.Run(() => NotifyApi(data));
}

private async Task NotifyApi(bool data)
{
    var toSend = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
    await client.PostAsync("http://...", data);
}

Trong trường hợp của tôi, tôi không quan tâm đến kết quả khi gọi phương thức không đồng bộ nhưng tôi đoán điều đó khá phổ biến trong trường hợp sử dụng này. Bạn có thể sử dụng kết quả trong phương thức không đồng bộ đang gọi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.