BackgroundWorker trong ứng dụng C# Windows Form

Giới thiệu

Hẳn đối với mỗi chúng ta, khi nhắc đến khái niệm Thread thì khá là hoang mang, và để handle được nhiều thread cùng chạy đồng thời, mượt mà, và tương tác lẫn nhau thì cần am hiểu về Thread khá tốt. C# hiện nay khá mạnh trong việc phát triển ứng dụng Desktop Application, và nó cũng sinh ra những class và thư viện khá hay để chúng ta có thể dễ dàng hơn trong việc chạy nhiều thread, giúp việc phát triển ứng dụng trở nên dễ dàng và tường minh hơn. Trong bài này mình sẽ giới thiệu với các bạn về BackgroundWorker, giúp cho việc chạy 1 thread mới hiệu quả.

Sự cần thiết

Một chương trình Windows cơ bản khi chạy thì main thread đầu tiên là UI thread. UI thread này chịu trách nhiệm cho việc tạo và render các controls và ánh xạ các kết quả mà code trả về trên UI. Vì vậy khi bạn chạy 1 task mà cần nhiều thời gian (như lấy dữ liệu từ database, hoặc xử lý các ảnh phức tạp chẳng hạn), thì thread UI này có thể bị lock và đơ vì lúc này main thread đang phải làm task khác ko phải render UI, dẫn đến chương trình của bạn bị Not Responding.

Vì vậy chúng ta cần 1 thread khác để chịu trách nhiệm cho việc lấy dữ liệu hay xử lý ảnh ở trên, không can thiệp đến thread UI đang chạy, và thread này có gọi các event để thay đổi UI khi nó xong nhiệm vụ. BackgroundWorker object được sinh ra để giúp chúng ta đơn giản hóa việc tạo và handle thread mới này.

Sử dụng BackroundWorker

Tạo object và handle DoWork

Khá đơn giản, chỉ cần những bước sau:

  • Tạo 1 BackgroundWorker object
  • Buộc BackgroundWorker object làm việc khác (như lấy database, xử lý ảnh) -> DoWork
  • Khi làm xong việc thì nói với threadUI để nó render ra cho người dùng biết là đã xong -> RunWorkerCompleted

Một nguyên tắc cần nhớ là không bao giờ được access UI objects trong 1 thread không tạo ra nó, vì 2 thread này độc lập với nhau. Ví dụ như trong function DoWork của BackroundWorker object, không được thay đổi các giá trị của UI ở trong hàm này, nó sẽ throw ra runtime error ngay. [__strong__]lblStatus.Text = "Processing file...20%"; //code này sẽ đưa ra lỗi nếu đặt ở trong DoWork function

BackgroundWorker giải quyết vấn đề này bằng cách cho chúng ta hàm ReportProgress, có thể gọi từ hàm DoWork , hàm này sẽ fire 1 event là ProgressChanged để thay đổi các giá trị ở UI. Thật tuyệt :v

BackgroundWoker cần cung cấp cho chúng ta hàm RunWorkerCompleted để fire event ra UI thread khi hàm DoWork đã chạy xong, giúp cho việc update kết quả cuối cùng của thread được tạo ra bằng BackgroundWorker ra ngoài UI.

Để report progress, chúng ta cần:

  • Set thuộc tính WorkerReportsProgress bằng true
  • Sau khi xong 1 task nhỏ ở DoWork thì call hàm ReportProgress để cập nhật lượng công việc đã hoàn thành myBackgroundWorker.ReportProgress(i);
  • Handle ProgressChanged event để update trạng thái việc đã hoàn thành ra UI
void myBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
            lbStatus.Text = "Processing......" + progressBar1.Value.ToString() + "%";
        }

Để cancel thread đang chạy, ta chỉ cần:

  • Set thuộc tính WorkerSupportsCancellation bằng true
  • Kiểm tra thuộc tính CancellationPending ở hàm DoWork, nếu nó bằng true thì set thuộc tính Cancel của event arguments là True, để nó gọi đến hàm RunWorkerCompleted luôn, kết thúc Thread.
if (myBackgroundWorker.CancellationPending)
                {
                    e.Cancel = true;
                    myBackgroundWorker.ReportProgress(0);
                    return;
                }
  • Gọi hàm CancelAsync để hủy thread
if (myBackgroundWorker.IsBusy)
            {
                myBackgroundWorker.CancelAsync();
            }

Các thuộc tính hữu ích

Argument, Result properties và RunWokerAsync method của BackroundWorker là những phần chúng ta cần nhớ để có thể handle được BackgroundWorker hiệu quả nhất.

  • DoWorkEventArgs e bao gồm** e.Argument** và e.Result, được dùng để access những thuộc tính của BackgroundWorker
  • e.Argument sử dụng để lưu những tham số đầu vào nhận bởi RunWorkerAsync -** e.Result** dùng để kiểm tra những output, hay kết quả mà BackgroundWorker đã làm, report lại
  • **myBackgroundWorker.RunWorkerAsync(); **dùng để bắt đầu cho thread chạy

Hy vọng bài viết có chút hữu ích cho các bạn. Đây là source code của chương trình: https://github.com/furytara/backgroundWorker

Bài viết có tham khảo từ https://www.codeproject.com/Articles/99143/BackgroundWorker-Class-Sample-for-Beginners