2014-08-28 16 views
8

Tôi đã có một tình huống gần đây, nơi tôi có một bộ điều khiển ASP.NET WebAPI cần thiết để thực hiện hai yêu cầu web cho một dịch vụ REST khác bên trong phương thức hành động của nó. Tôi đã viết mã của tôi để có chức năng tách sạch vào phương pháp riêng biệt, trông hơi giống như ví dụ sau:Cách hoạt động của chuỗi cuộc gọi async/await trả về?

public class FooController : ApiController 
{ 

    public IHttpActionResult Post(string value) 
    { 
     var results = PerformWebRequests(); 
     // Do something else here... 
    } 

    private IEnumerable<string> PerformWebRequests() 
    { 
     var result1 = PerformWebRequest("service1/api/foo"); 
     var result = PerformWebRequest("service2/api/foo"); 

     return new string[] { result1, result2 }; 
    } 

    private string PerformWebRequest(string api) 
    { 
     using (HttpClient client = new HttpClient()) 
     { 
      // Call other web API and return value here... 
     } 
    } 

} 

Bởi vì tôi đã sử dụng HttpClient tất cả các yêu cầu web phải được async. Tôi đã không bao giờ được sử dụng async/await trước khi vì vậy tôi bắt đầu ngây thơ thêm vào các từ khóa. Đầu tiên tôi thêm từ khóa async vào phương thức PerformWebRequest(string api) nhưng sau đó người gọi phàn nàn rằng phương pháp PerformWebRequests() phải là async để sử dụng await. Vì vậy, tôi đã thực hiện điều đó async nhưng bây giờ người gọi của phương pháp đó phải là async quá, v.v.

Những gì tôi muốn biết là cách xa lỗ thỏ phải mọi thứ được đánh dấu async để hoạt động? Chắc chắn sẽ có một điểm mà một cái gì đó phải chạy đồng bộ, trong trường hợp đó được xử lý một cách an toàn như thế nào? Tôi đã đọc rằng gọi Task.Result là một ý tưởng tồi bởi vì nó có thể gây ra deadlocks.

+0

Tôi đang tranh luận xem điều này có đủ điều kiện là trùng lặp hay không: http://stackoverflow.com/questions/9208921/async-on-main-method-of-console-app – spender

+1

bạn có thể xem video tuyệt vời này về tính năng không đồng bộ trên kênh9 - http://channel9.msdn.com/events/TechDays/Techdays-2014-the-Netherlands/Async-programming-deep-dive – terrybozzio

+1

Một tài nguyên tuyệt vời khác là loạt bài đăng trên blog của Eduasync của Jon Skeet http: // codeblog. jonskeet.uk/category/eduasync/ – softveda

Trả lời

13

Điều tôi muốn biết là lỗ hổng cách xa lỗ thỏ phải cách nào được đánh dấu là không đồng bộ để hoạt động? Chắc chắn sẽ có một điểm mà tại đó một cái gì đó phải chạy đồng bộ

Không, không nên có bất kỳ điểm nào chạy đồng bộ và đó là điều gì không đồng bộ. Cụm từ "không đồng bộ tất cả các cách" thực sự có nghĩa là tất cả các cách lên ngăn xếp cuộc gọi.

Khi bạn xử lý thư không đồng bộ, bạn sẽ cho phép yêu cầu xử lý vòng lặp thư trong khi phương pháp thực sự không đồng bộ của bạn chạy, bởi vì khi bạn đi sâu xuống hố, There is no Thread.

Ví dụ, khi bạn có một nút async sự kiện click handler:

private async void Button_Click(object sender, RoutedEventArgs e) 
{ 
    await DoWorkAsync(); 
    // Do more stuff here 
} 

private Task DoWorkAsync() 
{ 
    return Task.Delay(2000); // Fake work. 
} 

Khi nút được nhấn vào, chạy đồng bộ cho đến khi nhấn await đầu tiên. Sau khi nhấn, phương thức sẽ kiểm soát trở lại người gọi, điều đó có nghĩa là trình xử lý sự kiện nút sẽ giải phóng chuỗi giao diện người dùng, điều này sẽ giải phóng vòng lặp tin nhắn để xử lý nhiều yêu cầu hơn trong khi đó.

Điều tương tự cũng xảy ra khi bạn sử dụng HttpClient. Ví dụ, khi bạn có:

public async Task<IHttpActionResult> Post(string value) 
{ 
    var results = await PerformWebRequests(); 
    // Do something else here... 
} 

private async Task<IEnumerable<string>> PerformWebRequests() 
{ 
    var result1 = await PerformWebRequestAsync("service1/api/foo"); 
    var result = await PerformWebRequestAsync("service2/api/foo"); 

    return new string[] { result1, result2 }; 
} 

private async string PerformWebRequestAsync(string api) 
{ 
    using (HttpClient client = new HttpClient()) 
    { 
     await client.GetAsync(api); 
    } 

    // More work.. 
} 

Xem cách từ khóa async lên tất cả các cách để các phương pháp chính xử lý yêu cầu POST. Bằng cách đó, trong khi yêu cầu http không đồng bộ được xử lý bởi trình điều khiển thiết bị mạng, luồng của bạn trả về ASP.NET ThreadPool và được tự do xử lý nhiều yêu cầu hơn trong khi đó.

Ứng dụng bảng điều khiển là trường hợp đặc biệt, vì khi phương pháp Main chấm dứt, trừ khi bạn quay chủ đề tiền cảnh mới, ứng dụng sẽ chấm dứt.Ở đó, bạn phải đảm bảo rằng nếu cuộc gọi duy nhất là cuộc gọi không đồng bộ, bạn sẽ phải sử dụng rõ ràng Task.Wait hoặc Task.Result. Nhưng trong trường hợp đó, mặc định SynchronizationContextThreadPoolSynchronizationContext, nơi không có cơ hội gây ra bế tắc.

Để kết thúc, các phương pháp không đồng bộ không được xử lý đồng bộ ở đầu ngăn xếp, trừ khi có trường hợp sử dụng kỳ lạ (chẳng hạn như ứng dụng Console), chúng nên chạy không đồng bộ tất cả cách cho phép chuỗi được giải thoát khi có thể.

+0

Câu trả lời hay. Là một tối ưu hóa, người ta cũng có thể tạo ra trường hợp mà hai lệnh gọi đến 'PerformWebRequestAsync' có thể được thực hiện song song và được chờ đợi với' Task.WhenAll'. Tất nhiên, điều này giả định rằng kết quả của cuộc gọi đầu tiên là không cần thiết theo yêu cầu của cuộc gọi thứ hai. –

+0

@MattJohnson Cảm ơn matt, tôi nghĩ về điều đó, nhưng không muốn thay đổi mã gốc của OP quá nhiều, để đưa anh ta vào bối cảnh bên trong câu trả lời. –

+1

Cảm ơn bạn @ YuvalItzchakov, điều đó giải thích rất nhiều cho tôi, đặc biệt là khi được sử dụng trong phương thức 'Main()' (mà tôi cũng tự hỏi). Âm thanh với tôi rằng trong khi 'async/await' làm cho việc này dễ dàng hơn nhiều, có những cân nhắc về thiết kế để suy nghĩ trước khi chỉ đẩy nó vào - như hiểu được bạn có thể thay đổi bao nhiêu mã để thêm ngay cả một phương thức 'async'. –

0

Bạn cần "không đồng bộ tất cả các con đường lên" ở phía trên cùng của ngăn xếp cuộc gọi, nơi bạn tiếp cận vòng lặp thông báo có thể xử lý tất cả các yêu cầu không đồng bộ.

Các vấn đề liên quan