2013-07-31 34 views
17

Tôi đã nhận được loại điều này làm việc trong quá khứ với một BackgroundWorker, nhưng tôi muốn sử dụng phương pháp tiếp cận async/await mới của .NET 4.5. Tôi có thể sủa cây sai. Xin cho biết.Async/Await with WinForms ProgressBar

Mục tiêu: Tạo thành phần sẽ thực hiện một số tác vụ dài và hiển thị biểu mẫu có thanh tiến trình khi đang thực hiện công việc. Các thành phần sẽ có được xử lý để một cửa sổ để ngăn chặn sự tương tác trong khi nó thực hiện công việc lâu dài.

Trạng thái: Xem mã bên dưới. Tôi nghĩ tôi đã làm tốt cho đến khi tôi thử tương tác với các cửa sổ. Nếu tôi để mọi thứ một mình (nghĩa là đừng chạm vào!), Mọi thứ sẽ chạy "hoàn hảo", nhưng nếu tôi làm như vậy thì nhấp vào một trong hai cửa sổ chương trình sẽ bị treo sau khi kết thúc công việc dài hạn. Tương tác thực tế (kéo) bị bỏ qua như thể chuỗi giao diện người dùng bị chặn.

Câu hỏi: Mã của tôi có thể được sửa dễ dàng không? Nếu vậy, làm thế nào? Hoặc, tôi có nên sử dụng một cách tiếp cận khác (ví dụ: BackgroundWorker) không?

(Form1 là một hình thức tiêu chuẩn với một ProgressBar và một phương pháp công cộng, UpdateProgress, mà bộ giá trị gia tăng của ProgressBar):

using System; 
using System.Diagnostics; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace ConsoleApplication1 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine("Starting.."); 
     var mgr = new Manager(); 
     mgr.GoAsync(); 
     Console.WriteLine("..Ended"); 
     Console.ReadKey(); 
    } 
} 

class Manager 
{ 
    private static Form1 _progressForm; 

    public async void GoAsync() 
    { 
     var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); 
     _progressForm = new Form1(); 
     _progressForm.Show(owner); 

     await Go(); 

     _progressForm.Hide(); 
    } 

    private async Task<bool> Go() 
    { 
     var job = new LongJob(); 
     job.OnProgress += job_OnProgress; 
     job.Spin(); 
     return true; 
    } 

    void job_OnProgress(int percent) 
    { 
     _progressForm.UpdateProgress(percent); 
    } 
} 

class LongJob 
{ 
    public event Progressed OnProgress; 
    public delegate void Progressed(int percent); 

    public void Spin() 
    { 
     for (var i = 1; i <= 100; i++) 
     { 
      Thread.Sleep(25); 
      if (OnProgress != null) 
      { 
       OnProgress(i); 
      } 
     } 
    } 
} 

class Win32Window : IWin32Window 
{ 
    private readonly IntPtr _hwnd; 
    public Win32Window(IntPtr handle) 
    { 
     _hwnd = handle; 
    } 
    public IntPtr Handle 
    { 
     get 
     { 
      return _hwnd; 
     } 
    } 
} 
} 

Trả lời

7

@ Câu trả lời của StephenCleary là chính xác. Mặc dù, tôi đã phải sửa đổi một chút để trả lời của anh ta để có được hành vi mà tôi nghĩ OP muốn.

public void GoAsync() //no longer async as it blocks on Appication.Run 
{ 
    var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); 
    _progressForm = new Form1(); 

    var progress = new Progress<int>(value => _progressForm.UpdateProgress(value)); 

    _progressForm.Activated += async (sender, args) => 
     { 
      await Go(progress); 
      _progressForm.Close(); 
     }; 

    Application.Run(_progressForm); 
} 
+0

nếu Go (tiến trình) ném một ngoại lệ, sau đó _progressForm.Close() sẽ không bao giờ được gọi -> hộp thoại phương thức sẽ treo mãi mãi – Hiep

+0

Và tốt hơn nên thay đổi "_progressForm.Activated" thành "_progressForm .Shown" vì _progressForm có thể được kích hoạt nhiều lần trong suốt cuộc đời của mình .. -> Go (tiến trình) sẽ được gọi nhiều lần .. – Hiep

+0

https://gist.github.com/duongphuhiep/f83f98593d93045e717f – Hiep

18

Các asyncawait từ khóa không có nghĩa là "chạy trên nền chuỗi. " Tôi có một số async/await intro on my blog mô tả ý nghĩa của chúng là làm. Bạn phải đặt rõ ràng các hoạt động bị ràng buộc CPU trên một chuỗi nền, ví dụ: Task.Run.

Ngoài ra, tài liệu Task-based Asynchronous Pattern mô tả các phương pháp chung với mã số async, ví dụ: báo cáo tiến độ.

class Manager 
{ 
    private static Form1 _progressForm; 

    public async Task GoAsync() 
    { 
    var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); 
    _progressForm = new Form1(); 
    _progressForm.Show(owner); 

    var progress = new Progress<int>(value => _progressForm.UpdateProgress(value)); 
    await Go(progress); 

    _progressForm.Hide(); 
    } 

    private Task<bool> Go(IProgress<int> progress) 
    { 
    return Task.Run(() => 
    { 
     var job = new LongJob(); 
     job.Spin(progress); 
     return true; 
    }); 
    } 
} 

class LongJob 
{ 
    public void Spin(IProgress<int> progress) 
    { 
    for (var i = 1; i <= 100; i++) 
    { 
     Thread.Sleep(25); 
     if (progress != null) 
     { 
     progress.Report(i); 
     } 
    } 
    } 
} 

Lưu ý rằng loại Progress<T> đúng cách xử lý đề marshaling, vì vậy không cần cho marshaling trong Form1.UpdateProgress.

+0

Thay đổi của bạn không tạo ra kết quả mong muốn. Vì OP đang chạy trong một ứng dụng 'Console', không có' SynchronizationContext'. Tôi nghĩ rằng nó cần phải được giới thiệu cho 'Progress ' để hoạt động chính xác? – YK1

+0

Cấu phần Windows Forms sẽ thiết lập một 'WinFormsSynchronizationContext' khi chúng được tạo và' Progress 'không yêu cầu một 'SynchronizationContext' (mặc dù nó hoạt động tốt hơn với một). –

+0

Tôi đặt một điểm break vào đại biểu được chuyển đến 'Progress ' - nó không bao giờ bị trúng - UI chỉ bị treo mặc dù công việc đang quay. Tôi nghĩ 'Application.Run()' đã được yêu cầu để thiết lập 'SynchronizationContext' - hiện' _progressForm.Show' thiết lập một? Tôi không chắc. – YK1

3
private async void button1_Click(object sender, EventArgs e) 
{ 
    IProgress<int> progress = new Progress<int>(value => { progressBar1.Value = value; }); 
    await Task.Run(() => 
    { 
     for (int i = 0; i <= 100; i++) 
      progress.Report(i); 
    }); 
} 

Đúng tôi nếu tôi là sai, nhưng điều này có vẻ là cách dễ nhất để cập nhật một thanh tiến trình.