C# – Lập trình Thread với BackgroundWorker

BackgroundWorker là một component giúp cho việc lập trình Thread trở nên dễ dàng do các thuộc tính và event mà nó hỗ trợ sẵn. Trong bài viết này tôi sẽ giới thiệu cách sử dụng BackgroundWoker để làm một ví dụ tìm kiếm tập tin trong một dự án Windows Form. Qua đó bạn có thể thấy khả năng của đối tượng này và áp dụng vào những chương trình cụ thể.

Giới thiệu

Namespace:  System.ComponentModel
Assembly:  System (in System.dll)

Các property, method, event mà bạn cần quan tâm:

Properties

NameDescription
CancellationPendingGets a value indicating whether the application has requested cancellation of a background operation.
IsBusyGets a value indicating whether the BackgroundWorker is running an asynchronous operation.
WorkerReportsProgressGets or sets a value indicating whether the BackgroundWorker can report progress updates.
WorkerSupportsCancellationGets or sets a value indicating whether the BackgroundWorker supports asynchronous cancellation.

Methods

NameDescription
CancelAsyncRequests cancellation of a pending background operation.
OnDoWorkRaises the DoWork event.
OnProgressChangedRaises the ProgressChanged event.
OnRunWorkerCompletedRaises the RunWorkerCompleted event.
ReportProgress(Int32)Raises the ProgressChanged event.
ReportProgress(Int32, Object)Raises the ProgressChanged event.
RunWorkerAsync()Starts execution of a background operation.
RunWorkerAsync(Object)Starts execution of a background operation.

Events

NameDescription
DoWorkOccurs when RunWorkerAsync is called.
ProgressChangedOccurs when ReportProgress is called.
RunWorkerCompletedOccurs when the background operation has completed, has been canceled, or has raised an exception.

Để sử dụng BackgroundWorker trong Windows Form, bạn có thể kéo component vào từ toolbox trong thẻ Components và sử dụng như các control thông thường.

Phương thức chính mà BackgroundWorker sẽ thực thi là OnDoWork(). BackgroundWorker sẽ tạo ra một Thread mới với chế độ chạy nền (Thread.IsBackground = true) để thực thi các dòng lệnh trong phương thức này. Tương tự khi lập trình Thread trong Windows Form, bạn nên cẩn thận khi truy xuất đến các control của Form trực tiếp để tránh các exception: ”Cross-thread operation not valid”.

Quy trình làm việc của BackgroundWorker

–          Gọi RunWorkerAsync()

–          Phương thức OnDoWork() được thực thi

–          Thuộc tính IsBusy sẽ có giá trị true

–          Khi OnDoWork() thực hiện xong, OnRunWorkerCompleted() sẽ được gọi.

Cập nhật tiến độ công việc: Trong quá trình BackgroundWorker đang thực thi, bạn có thể dùng phương thức ReportProgress() với tham số là phần trăm tiến độ hoàn thành công việc để kích hoạt sự kiện ProgressChanged nhằm cập nhật tiến độ thực hiện công việc lên màn hình. Để sử dụng được tính năng này, bạn cần gán thuộc tính WorkerReportsProgress  bằng true.

Hủy bỏ việc thực thi: Bạn có thể gọi phương thức CancelAsync() trong lúc BackgroundWorker đang hoạt động để dừng công việc đang thực hiện. Khi đó thuộc tính CancellationPending sẽ có giá trị true. Mặc dù bạn gọi phương thức CancelAsync() nhưng công việc có thể vẫn tiếp tục hoạt động, vì vậy bạn cần dựa vào CancellationPending để kiểm tra và ngừng công việc lại. Để sử dụng được tính năng này, bạn cần gán WorkerSupportsCancellation bằng true.

Truy xuất đến các control của Form: để làm được điều này bạn có thể đặt Control.CheckForIllegalCrossThreadCalls bằng true. Tuy nhiên cách chính quy là gọi thông qua các delegate. Sử dụng phương thức Invoke() để gọi một delegate với cú pháp của anonymous method hoặc lambda expression. Ví dụ:

myControl.Invoke((Action)(()=>lblProgress.Text=fileName));

Ví dụ: Tìm kiếm tập tin trong Windows Form

Screenshot:

C# Source Code:

using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;

namespace Y2SimpleFileFinder
{
    public partial class MainForm : Form
    {
        BackgroundWorker backgroundWorker1;
        public MainForm()
        {
            InitializeComponent();

            backgroundWorker1 = new BackgroundWorker();
            backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.WorkerSupportsCancellation = true;

            backgroundWorker1.DoWork += backgroundWorker1_DoWork;
            backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
            backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;

        }
        private void btnSearch_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.IsBusy)
            {
                backgroundWorker1.CancelAsync();
            }
            else
            {
                progressBar1.Value = progressBar1.Minimum;
                btnSearch.Text = "Stop";
                listView1.Items.Clear();
                backgroundWorker1.RunWorkerAsync();
            }
        }
        void AddToListView(string file)
        {
            FileInfo finfo = new FileInfo(file);
            ListViewItem item = new ListViewItem(finfo.Name);
            item.SubItems.Add(finfo.DirectoryName);
            item.SubItems.Add(Math.Ceiling(finfo.Length / 1024f).ToString("0 KB"));

            listView1.Invoke((Action)(() =>
                {
                    listView1.BeginUpdate();
                    listView1.Items.Add(item);
                    listView1.EndUpdate();
                }));

        }

        void ScanDirectory(string directory, string searchPattern)
        {
            try
            {
                foreach (var file in Directory.GetFiles(directory))
                {
                    if (backgroundWorker1.CancellationPending)
                    {
                        return;
                    }

                    lblProgress.Invoke((Action)(() => lblProgress.Text = file));
                    if (file.Contains(searchPattern))
                    {
                        AddToListView(file);
                    }
                }

                foreach (var dir in Directory.GetDirectories(directory))
                {
                    ScanDirectory(dir, searchPattern);
                }
            }
            catch
            {
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {

            string[] dirs = Directory.GetDirectories(txtFolderPath.Text);
            float length = dirs.Length;
            progressBar1.Invoke((Action)(() => progressBar1.Maximum = dirs.Length));
            for (int i = 0; i < dirs.Length; i++)
            {
                backgroundWorker1.ReportProgress((int)(i / length * 100));
                ScanDirectory(dirs[i], txtSearch.Text);
            }

            backgroundWorker1.ReportProgress(100);

        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (!backgroundWorker1.CancellationPending)
            {
                lblPercent.Text = e.ProgressPercentage + "%";
                progressBar1.PerformStep();
            }
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

            lblProgress.Text = String.Format("{0} files found", listView1.Items.Count);
            if (progressBar1.Value < progressBar1.Maximum)
            {
                lblProgress.Text = "Searching cancelled. " + lblProgress.Text;
            }
            btnSearch.Text = "Search";
        }

        private void btnBrowser_Click(object sender, EventArgs e)
        {
            if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
            {
                txtFolderPath.Text = folderBrowserDialog1.SelectedPath;
            }
        }

    }
}