2014-08-28 23 views
6

Nói rằng tôi có các định nghĩa lớp sau đây:thực hành tốt nhất về việc sử dụng async/chờ đợi

public class Calculator 
{ 
    public CalculatorResult Calculate() 
    { 
     return LongRunningCalculation(); 
    } 

    private CalculatorResult LongRunningCalculation() 
    { 
     return new CalculatorResult(0.00); 
    } 
} 

public class ClassThatUsesACalculator 
{ 
    private readonly Calculator calculator; 

    public ClassThatUsesACalculator() 
    { 
     this.calculator = new Calculator(); 
    } 

    public void DoWork() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      var result = calculator.Calculate(); 

      DoSomethingWithCalculationResult(result); 

      DoLightWork(); 

      OnProgressChanged(); 
     } 
    } 
} 

public partial class Form : Form 
{ 
    public Form() 
    { 
     InitializeComponent(); 
    } 

    private void Method(object sender, EventArgs e) 
    { 
     DoWork(); 
    } 

    private void DoWork() 
    { 
     var calculator = new ClassThatUsesACalculator(); 
     calculator.ProgressChanged += (s, e) => 
     { 
      // Update progressbar 
     }; 

     calculator.DoWork(); 
    } 
} 

Nếu tôi muốn làm công việc thực hiện trong DoWork(), về hình thức, không đồng bộ tôi có thể thêm một phương pháp (GetCalculationTask) trả về một tác vụ bằng cách sử dụng Task.Run() và thêm một sự kiện đồng bộ hóa tức thì cho một nút (MethodOne).

Vui lòng sửa tôi nếu tôi sai, nhưng có vẻ như đây là lựa chọn duy nhất khi các lớp học ClassThatUsesACalculatorCalculator nằm trong thư viện mà tôi không sở hữu.

private Task GetCalculationTask(IProgress<CalculatorProgress> progress) 
{ 
    var calculator = new ClassThatUsesACalculator(); 
    calculator.ProgressChanged += (s, e) => 
    { 
     progress.Report(new CalculatorProgress(0)); 
    }; 

    return Task.Run(() => 
    { 
     calculator.DoWork(); 
    }); 
} 

private async void MethodOne(object sender, EventArgs e) 
{ 
    IProgress<CalculatorProgress> progress = new Progress<CalculatorProgress> (UpdateProgressBar); 

    await GetCalculationTask(progress); 
} 

Trong trường hợp tôi sở hữu thư viện, tôi nghĩ có thêm hai tùy chọn, một trong số đó rất giống tùy chọn đầu tiên. Có lẽ do sự thiếu hiểu biết của tôi.

Tạo phương thức trên ClassThatUsesACalculator đóng gói phương thức DoWork() và sau đó gọi phương thức đó từ phương thức không đồng bộ trên biểu mẫu.

hay,

  1. đóng gói các LongRunningCalculation() trên lớp Calculator với một Task.Run().

    public Task<CalculatorResult> CalculateAsync() 
    { 
        return Task.Run(() => 
        { 
         return LongRunningCalculation(); 
        }); 
    } 
    
  2. Tạo một phương thức không đồng bộ trên ClassThatUsesACalculator cuộc gọi đang chờ phương thức mới được tạo.

    public async Task DoWorkAsync() 
    { 
        for (int i = 0; i < 10; i++) 
        { 
         var result = await calculator.CalculateAsync(); 
    
         DoSomethingWithCalculationResult(result); 
    
         DoLightWork(); 
    
         OnProgressChanged(); 
        } 
    } 
    
  3. Tạo một phương pháp không đồng bộ về hình thức (MethodThree)

    private async void MethodThree(object sender, EventArgs e) 
    { 
        IProgress<CalculatorProgress> progress = new Progress<CalculatorProgress>(UpdateProgressBar); 
    
        var calculator = new ClassThatUsesACalculator(); 
        calculator.ProgressChanged += (s, args) => 
        { 
         progress.Report(new CalculatorProgress(0)); 
        }; 
    
        await calculator.DoWorkAsync(); 
    } 
    

Bây giờ, theo ý kiến ​​của tôi lựa chọn cuối cùng sẽ là tốt nhất như tôi sẽ vẫn kiểm soát nhiều hơn. Nhưng có lẽ tôi đang đi và muốn ý kiến ​​của một ai đó hoặc con trỏ về điều này vì tôi chỉ có thể tìm thấy giải thích về cách tiêu thụ async, nhưng không bao giờ thực sự làm thế nào để xây dựng phương pháp cho người khác để tiêu thụ.

+5

Câu hỏi của bạn thực sự dài và quá dài dòng - nó có thể hữu ích nếu bạn thu hẹp nó để giải thích ngắn gọn những gì bạn muốn biết. Nhân tiện, đối với những thứ nhanh chóng, 'async' không thực sự cần thiết. 'async' là tuyệt vời cho các nhiệm vụ I/O như truy cập một tệp, cơ sở dữ liệu hoặc dịch vụ web. Nhưng chi phí cho việc chạy nhanh các bit không phải là mã I/O thực sự có thể làm giảm hiệu suất. – mason

Trả lời

11

Như một quy tắc chung, hãy đẩy mọi mức sử dụng Task.Run lên mức cao nhất có thể.

Điều bạn muốn tránh đang có phương thức có chữ ký không đồng bộ được triển khai bằng cách sử dụng Task.Run trong thành phần có thể tái sử dụng. Đó là một API nói dối. Tôi có một số blog post on the subject để xem chi tiết hơn.

Nếu bạn kiểm soát các lớp được đề cập, tôi khuyên bạn nên sử dụng IProgress<T> thay vì các sự kiện để cập nhật tiến trình. IProgress<T> công trình tốt với mã đồng bộ cũng như không đồng bộ:

public void DoWork(IProgress<CalculatorProgress> progress = null) 
{ 
    for (int i = 0; i < 10; i++) 
    { 
    var result = calculator.Calculate(); 

    DoSomethingWithCalculationResult(result); 

    DoLightWork(); 

    if (progress != null) 
     progress.Report(new CalculatorProgress(...)); 
    } 
} 

Sau đó, sử dụng nó là khá đơn giản:

private async void MethodTwo(object sender, EventArgs e) 
{ 
    IProgress<CalculatorProgress> progress = new Progress<CalculatorProgress>(UpdateProgressBar); 

    var calculator = new ClassThatUsesACalculator(); 

    await Task.Run(() => calculator.DoWork(progress)); 
} 

Nó giúp duy trì việc sử dụng Task.Run trong thành phần mà cần nó - lớp UI - và ra khỏi logic kinh doanh.

+1

Điều này nên được hát từ đỉnh núi. – rmirabelle

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