'Chờ' hoạt động, nhưng gọi nhiệm vụ.Result bị treo / bế tắc


126

Tôi có bốn bài kiểm tra sau và bài kiểm tra cuối cùng bị treo khi tôi chạy nó. Lý do tại sao điều này xảy ra:

[Test]
public void CheckOnceResultTest()
{
    Assert.IsTrue(CheckStatus().Result);
}

[Test]
public async void CheckOnceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceResultTest()
{
    Assert.IsTrue(CheckStatus().Result); // This hangs
    Assert.IsTrue(await CheckStatus());
}

private async Task<bool> CheckStatus()
{
    var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
    Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
    IRestResponse<DummyServiceStatus> response = await restResponse;
    return response.Data.SystemRunning;
}

Tôi sử dụng phương thức mở rộng này cho restsharp RestClient :

public static class RestClientExt
{
    public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
    {
        var tcs = new TaskCompletionSource<IRestResponse<T>>();
        RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
        return tcs.Task;
    }
}
public class DummyServiceStatus
{
    public string Message { get; set; }
    public bool ValidVersion { get; set; }
    public bool SystemRunning { get; set; }
    public bool SkipPhrase { get; set; }
    public long Timestamp { get; set; }
}

Tại sao bài kiểm tra cuối cùng bị treo?


7
Bạn nên tránh trả về void từ các phương thức async. Nó chỉ dành cho khả năng tương thích ngược với các trình xử lý sự kiện hiện tại, chủ yếu là ở mã giao diện. Nếu phương thức async của bạn không trả về bất cứ điều gì, nó sẽ trả về Nhiệm vụ. Tôi đã gặp nhiều vấn đề với MSTest và bỏ trống các bài kiểm tra async.
ghord

2
@ghord: MSTest hoàn toàn không hỗ trợ async voidcác phương pháp kiểm tra đơn vị; họ chỉ đơn giản là không làm việc. Tuy nhiên, NUnit thì có. Điều đó nói rằng, tôi đồng ý với nguyên tắc chung là thích async Taskhơn async void.
Stephen Cleary

@StephenCleary Có, mặc dù điều đó được cho phép trong betas của VS2012, điều này gây ra tất cả các loại vấn đề.
ghord

Câu trả lời:


88

Bạn đang gặp phải tình huống bế tắc tiêu chuẩn mà tôi mô tả trên blog của mìnhtrong một bài viết về MSDN : asyncphương pháp này đang cố gắng lên lịch tiếp tục cho một chủ đề đang bị chặn bởi lệnh gọi đến Result.

Trong trường hợp này, cái của bạn SynchronizationContextlà cái được NUnit sử dụng để thực thi async voidcác phương thức kiểm tra. Tôi sẽ thử sử dụng async Taskcác phương pháp thử nghiệm thay thế.


4
Thay đổi thành async Nhiệm vụ đã hoạt động, bây giờ tôi cần đọc nội dung các liên kết của bạn một vài lần, thưa ông.
Johan Larsson

@MarioLopez: Giải pháp là sử dụng " asyncmọi cách" (như đã lưu ý trong bài viết MSDN của tôi). Nói cách khác - như tiêu đề của bài đăng trên blog của tôi nói - "không chặn mã async".
Stephen Cleary

1
@StephenCleary nếu tôi phải gọi một phương thức async bên trong hàm tạo thì sao? Các nhà xây dựng không thể không đồng bộ.
Raikol Amaro

1
@StephenCleary Trong gần như tất cả các câu trả lời của bạn về SO và trong các bài viết của bạn, tất cả những gì tôi từng thấy bạn nói về việc thay thế Wait()bằng cách thực hiện phương thức gọi điện async. Nhưng với tôi, điều này dường như đang đẩy vấn đề ngược dòng. Tại một số điểm, một cái gì đó phải được quản lý đồng bộ. Điều gì xảy ra nếu chức năng của tôi có mục đích đồng bộ vì nó quản lý các luồng công nhân chạy dài với Task.Run()? Làm thế nào để tôi chờ đợi điều đó kết thúc mà không bị bế tắc trong bài kiểm tra NUnit của mình?
void.pulum

1
@ void.pulum: At some point, something has to be managed synchronously.- hoàn toàn không. Đối với các ứng dụng UI, điểm vào có thể là một async voidtrình xử lý sự kiện. Đối với các ứng dụng máy chủ, điểm vào có thể là một async Task<T>hành động. Tốt nhất là sử dụng asynccho cả hai để tránh chặn chủ đề. Bạn có thể có bài kiểm tra NUnit của mình là đồng bộ hoặc không đồng bộ; nếu không đồng bộ, làm cho nó async Taskthay vì async void. Nếu nó đồng bộ, nó không SynchronizationContextnên có một bế tắc.
Stephen Cleary

222

Có được giá trị thông qua phương thức async:

var result = Task.Run(() => asyncGetValue()).Result;

Gọi đồng bộ một phương thức async

Task.Run( () => asyncMethod()).Wait();

Không có vấn đề bế tắc sẽ xảy ra do việc sử dụng Task.Run.


15
-1 để khuyến khích sử dụng các async voidphương pháp thử nghiệm đơn vị và loại bỏ các bảo đảm cùng luồng được cung cấp bởi SynchronizationContexthệ thống được thử nghiệm.
Stephen Cleary

68
@StephenCleary: không có "sự khích lệ" của khoảng trống async. Nó chỉ đơn thuần sử dụng cấu trúc c # hợp lệ để giải quyết vấn đề bế tắc. Đoạn mã trên là một công việc đơn giản và không thể thiếu đối với vấn đề của OP. Stackoverflow là về các giải pháp cho các vấn đề, không phải tự quảng cáo dài dòng.
Herman Schoenfeld

81
@StephenCleary: Bài viết của bạn không thực sự nói rõ giải pháp (ít nhất là không rõ ràng) và ngay cả khi bạn có giải pháp, bạn sẽ sử dụng các cấu trúc đó một cách gián tiếp. Giải pháp của tôi không sử dụng rõ ràng bối cảnh, vậy thì sao? Vấn đề là, công việc của tôi và nó là một lớp lót. Không cần hai bài đăng blog và hàng ngàn từ để giải quyết vấn đề. LƯU Ý: Tôi thậm chí không sử dụng khoảng trống async , vì vậy tôi thực sự không biết bạn đang nói về điều gì .. Bạn có thấy "async void" ở bất cứ đâu trong câu trả lời ngắn gọn và đúng đắn của tôi không?
Herman Schoenfeld

15
@HermanSchoenfeld, nếu bạn thêm lý do tại sao đến thế nào , tôi tin rằng câu trả lời của bạn sẽ được hưởng lợi rất nhiều.
iron1313

19
Tôi biết điều này là muộn, nhưng bạn nên sử dụng .GetAwaiter().GetResult()thay vì .Resultđể bất kỳ Exceptionkhông được bọc.
Camilo Terevinto

15

Bạn có thể tránh bế tắc khi thêm ConfigureAwait(false)vào dòng này:

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);

Tôi đã mô tả cạm bẫy này trong bài đăng trên blog Cạm bẫy của async / await


9

Bạn đang chặn UI bằng cách sử dụng thuộc tính Task.Result. Trong Tài liệu MSDN họ đã đề cập rõ ràng rằng,

" Thuộc tính Kết quả là thuộc tính chặn. Nếu bạn cố truy cập vào nó trước khi tác vụ của nó kết thúc, luồng hiện đang hoạt động sẽ bị chặn cho đến khi tác vụ hoàn thành và giá trị khả dụng. Trong hầu hết các trường hợp, bạn nên truy cập giá trị bằng cách sử dụng Await hoặc chờ đợi thay vì truy cập trực tiếp vào tài sản. "

Giải pháp tốt nhất cho kịch bản này là loại bỏ cả chờ đợi & không đồng bộ khỏi các phương thức và chỉ sử dụng Tác vụ khi bạn trả về kết quả. Nó sẽ không làm rối trình tự thực hiện của bạn.


3

Nếu bạn không nhận được bất kỳ cuộc gọi lại nào hoặc điều khiển bị treo, sau khi gọi hàm async dịch vụ / API, bạn phải định cấu hình Ngữ cảnh để trả về kết quả trong cùng bối cảnh được gọi.

Sử dụng TestAsync().ConfigureAwait(continueOnCapturedContext: false);

Bạn sẽ phải đối mặt với vấn đề này chỉ trong các ứng dụng web, nhưng không phải trong static void main.


ConfigureAwaittránh bế tắc trong các kịch bản nhất định bằng cách không chạy trong bối cảnh luồng gốc.
davidcarr
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.