2015-01-29 20 views
8

Tôi đã theo dõi this question và tôi hiểu lý do đằng sau câu trả lời phổ biến (mặc dù chưa được chấp nhận) bởi Peter Duniho. Cụ thể, Tôi biết rằng không chờ một hoạt động lâu dài sau này sẽ chặn thread UI:Nên lồng ghép các hoạt động chờ đợi được chờ đợi?

Ví dụ thứ hai không mang lại trong thời gian hoạt động không đồng bộ. Thay vào đó, bằng cách lấy giá trị của thuộc tính content.Result, bạn buộc luồng hiện tại phải đợi cho đến khi thao tác không đồng bộ hoàn tất.

Tôi thậm chí đã xác nhận điều này, vì lợi ích của riêng tôi, như vậy:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var value1 = await Task.Run(async() => 
     { 
      await Task.Delay(5000); 
      return "Hello"; 
     }); 

    //NOTE: this one is not awaited... 
    var value2 = Task.Run(async() => 
     { 
      await Task.Delay(5000); 
      return value1.Substring(0, 3); 
     }); 

    System.Diagnostics.Debug.Print(value2.Result); //thus, UI freezes here after 5000 ms. 
} 

Nhưng bây giờ tôi đang tự hỏi: Bạn cần phải await hoạt động tất cả các "awaitable" lồng trong một awaitable ngoài cùng hoạt động? Ví dụ, tôi có thể làm điều này:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var value0 = await Task.Run(() => 
     { 
      var value1 = new Func<Task<string>>(async() => 
      { 
       await Task.Delay(5000); 
       return "hello"; 
      }).Invoke(); 

      var value2 = new Func<string, Task<string>>(async (string x) => 
       { 
        await Task.Delay(5000); 
        return x.Substring(0, 3); 
       }).Invoke(value1.Result); 

      return value2; 
     }); 

    System.Diagnostics.Debug.Print(value0); 
} 

Hoặc tôi có thể làm điều này:

private async void button1_Click(object sender, EventArgs e) 
{ 
    //This time the lambda is async... 
    var value0 = await Task.Run(async() => 
     { 
      //we're awaiting here now... 
      var value1 = await new Func<Task<string>>(async() => 
      { 
       await Task.Delay(5000); 
       return "hello"; 
      }).Invoke(); 

      //and we're awaiting here now, too... 
      var value2 = await new Func<string, Task<string>>(async (string x) => 
       { 
        await Task.Delay(5000); 
        return x.Substring(0, 3); 
       }).Invoke(value1); 

      return value2; 
     }); 

    System.Diagnostics.Debug.Print(value0); 
} 

Và cả hai đều không đóng băng giao diện người dùng. Cái nào là thích hợp hơn?

+0

Tôi sẽ rất ngạc nhiên nếu những bài in điều tương tự, tôi tin rằng ai được in một đối tượng công tác thay vì kết quả của nó. –

+0

@BenVoigt - Không, bc trong cả hai trường hợp nhiệm vụ được mở ra vì nó đang được chờ đợi. –

+0

Nhưng một trong số đó là gói Nhiệm vụ thứ hai. –

Trả lời

13

Người cuối cùng là một lợi thế (mặc dù khá lộn xộn)

Trong TAP (công tác dựa trên mẫu Asynchronous) Một nhiệm vụ (và awaitables khác) đại diện cho một hoạt động không đồng bộ. Bạn có 3 lựa chọn về cơ bản xử lý các nhiệm vụ:

  • Chờ đồng bộ (DoAsync().Result, DoAsync().Wait()) - Khối tiểu trình đang gọi cho đến khi công việc được hoàn tất. Làm cho ứng dụng của bạn lãng phí hơn, ít khả năng mở rộng hơn, ít đáp ứng và dễ bị deadlocks hơn.
  • Chờ không đồng bộ (await DoAsync()) - Không chặn chuỗi cuộc gọi. Về cơ bản nó đăng ký công việc sau khi await như là một sự tiếp tục được thực thi sau khi nhiệm vụ được chờ đợi hoàn thành.
  • Đừng chờ chút nào (DoAsync()) - Không chặn chuỗi cuộc gọi, nhưng cũng không đợi thao tác hoàn tất. Bạn đang không biết về bất kỳ trường hợp ngoại lệ ném trong khi DoAsync được xử lý

Cụ thể, tôi biết rằng không phải chờ đợi một hoạt động lâu dài sau này sẽ chặn thread UI

Vì vậy, Không hẳn. Nếu bạn không chờ chút nào, không có gì sẽ chặn nhưng bạn không thể biết khi nào hoặc nếu hoạt động hoàn tất thành công. Tuy nhiên, nếu bạn chờ đồng bộ, bạn sẽ chặn chuỗi cuộc gọi và bạn có thể có deadlocks nếu bạn đang chặn chuỗi giao diện người dùng.

Kết luận: Bạn nên await các vật phẩm mong đợi của bạn miễn là có thể (ví dụ: không có trong ví dụ Main). Điều đó bao gồm "hoạt động lồng nhau async-await".

Giới thiệu về ví dụ cụ thể của bạn: Task.Run được sử dụng để giảm tải công việc bị ràng buộc CPU thành sợi ThreadPool, mà dường như không phải là những gì bạn đang cố gắng bắt chước.Nếu chúng ta sử dụng Task.Delay để đại diện cho một hoạt động thực sự không đồng bộ (thường là I/O-bound), chúng tôi có thể đã "lồng async-await" mà không Task.Run:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var response = await SendAsync(); 
    Debug.WriteLine(response); 
} 

async Task<Response> SendAsync() 
{ 
    await SendRequestAsync(new Request()); 
    var response = await RecieveResponseAsync(); 
    return response; 
} 

async Task SendRequestAsync(Request request) 
{ 
    await Task.Delay(1000); // actual I/O operation 
} 

async Task<Response> RecieveResponseAsync() 
{ 
    await Task.Delay(1000); // actual I/O operation 
    return null; 
} 

Bạn có thể sử dụng các đại biểu vô danh thay vì phương pháp này, nhưng nó không thoải mái khi bạn cần để xác định các loại và tự gọi chúng.

Nếu bạn cần phải giảm tải hoạt động mà to a thread ThreadPool, chỉ cần thêm Task.Run:

private async void button1_Click(object sender, EventArgs e) 
{ 
    var response = await Task.Run(() => SendAsync()); 
    Debug.WriteLine(response); 
} 
+0

Thực ra tôi đang đọc lại câu trả lời của bạn và tôi tự hỏi liệu bạn có thể làm rõ: bạn đang nói rằng tôi * không nên * sử dụng chờ đợi lồng nhau (đó là khối thứ ba của tôi đang làm - sau "Hoặc tôi có thể làm điều này")? –

+0

@roryap Tôi nói rằng nếu các hoạt động là 'async' thì' chờ đợi 'thay vì chặn chúng dù chúng ở đâu. Về việc có sử dụng 'Task.Run' không, một hoặc hai lần phụ thuộc vào những gì bạn đang * thực sự * cố gắng làm (tôi giả định' Task.Delay' ở đây chỉ là một sự thay thế). – i3arnon

+1

@roryap Nếu bạn có một hoạt động chuyên sâu CPU dài mà bạn muốn offload sử dụng 'Task.Run', nếu không đơn giản gọi các hoạt động' async' của bạn và 'await' chúng. – i3arnon

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