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

Name Description
CancellationPending Gets a value indicating whether the application has requested cancellation of a background operation.
IsBusy Gets a value indicating whether the BackgroundWorker is running an asynchronous operation.
WorkerReportsProgress Gets or sets a value indicating whether the BackgroundWorker can report progress updates.
WorkerSupportsCancellation Gets or sets a value indicating whether the BackgroundWorker supports asynchronous cancellation.

Methods

Name Description
CancelAsync Requests cancellation of a pending background operation.
OnDoWork Raises the DoWork event.
OnProgressChanged Raises the ProgressChanged event.
OnRunWorkerCompleted Raises 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

Name Description
DoWork Occurs when RunWorkerAsync is called.
ProgressChanged Occurs when ReportProgress is called.
RunWorkerCompleted Occurs 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;
            }
        }

    }
}

Download Demo+Sourcecode (VC# 2010)

—-

https://yinyangit.wordpress.com

16 thoughts on “C# – Lập trình Thread với BackgroundWorker

  1. Mình phát hiện ra 1 vấn đề: Đó là nếu bạn dùng BackgroundWorker để đôi lúc khi thao tác trên cơ sở dữ liệu, bạn lấy cùng 1 dòng và khi save dữ liệu lại, nó sẽ bị sai.
    Ví dụ: bạn lấy lên IsLogin = True, và bạn nhấp nút thoát, save lại là IsLogin = False, nhưng có 1 backgroundworker sau khi chạy xong tiến trình A, lưu thiết lập, nó save IsLogin = True. Vì vậy dù thoát nhưng vẫn bị đăng nhập lại.

    Trả lời
  2. Hi Yin Yang, mình có tìm hiểu thì thấy sử dụng Background Worker có thể giúp UI luôn trong trạng thái response khi thực hiện các xử lý nặng và lâu, tuy nhiên khi áp dụng thì ko được như vậy, đơn giản như call WCF service thôi cũng đủ khựng khựng, có thể do mình còn chưa rõ best practice cho việc giải quyết chuyện lag của UI. Bạn có thể giúp mình vấn đề này không, cảm ơn bạn nhiều.

    Trả lời
  3. hi, yinyangit. Mình có 1 thắc mắc, mong bạn giúp đỡ nhé.

    Mỗi khi chương trình gọi RunWorkerAsync() -> hàm OnDoWork sẽ được chạy. Khi đó 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 OnDoWork.
    Ở đây mình hiểu là mỗi khi chương trình gọi hàm RunWorkerAsync() 10 lần trong điều kiện BackgroundWorker không bận thì BackgroundWorker sẽ tạo ra 10 Thread có cùng nội dung được lập sẳn trong hàm OnDoWork. Không biết hình hiểu vậy có đúng không?

    Trả lời
    • Cảm ơn bạn, đây đúng là ko phải người mới bắt đầu học lập trình :D. Về phần lấy % thì nó tính dựa trên số thư mục con của thư mục cần tìm (ko tính thư mục cháu chắt). Ví dụ thư mục cần tìm chỉ có 4 thư mục con thì mỗi khi quét xong 1 thư mục đó nó sẽ tăng được 25%. Cách tính này tất nhiên là ko đủ chính xác nhưng cũng có thể coi là hợp lý.

      Trả lời
  4. Bạn có thể cho mình ví dụ hoặc link để tham khảo trường hợp:

    1- Hiển thị splash screen ( trong đó main form đang xử lý một số thao tác nền)

    2- Hiển thị một màn hìn Waiting… trong khi đang xử lý 1 thao tác nào đó

    Cám ơn bạn !

    Trả lời
  5. Pingback: C# – Lập trình Thread với BackgroundWorker | Lập Trình Pro

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s