+3

Background worker trong .NET

Hi anh em, chúc mọi người một ngày làm việc hiệu quả và đây năng lượng. Hôm nay mình sẽ giới thiệu về background worker trong .NET.

Không để anh em chờ lâu, cùng bắt đầu vào bài viết nào. Bài viết này mình sẽ nói về định nghĩa, các thuộc tính, các phương thức, sự kiện và cách sử dụng background worker trong .NET.

1. Background worker là gì?

  • BackgroundWorker là một class được cung cấp bởi .NET Framework để thực hiện các công việc tốn nhiều thời gian (long-running tasks) hoặc công việc đồng bộ trên một thread độc lập, giúp tránh tình trạng block ứng dụng khi thực hiện các tác vụ nặng.
  • BackgroundWorker thường được sử dụng trong các ứng dụng Windows Forms hoặc WPF để thực hiện các công việc tốn nhiều thời gian và cập nhật giao diện người dùng một cách liên tục.
  • Background worker không sử dụng được với .NET core. Nhưng .NET framework, .NET 5 và .NET 6 (long-term support) thì có support.

Note: nhưng có 1 lưu ý là với .NET 5 và .NET 6 không khuyến khích sử dụng Background worker. Khuyến khích sử dụng Task-based Asynchronous Programming (TAP) như tasks, async, await.. để thực hiện các công việc tốn thời gian dài (long-running tasks).

2. Lợi ích khi sử dụng background worker

  • Tránh block UI: Nếu bạn thực hiện các công việc nặng trong chương trình chính (UI thread), nó có thể gây block ứng dụng, khiến người dùng không thể tương tác với ứng dụng trong khi các công việc này đang được thực hiện. BackgroundWorker giúp thực hiện các công việc này trên một thread độc lập, tránh block ứng dụng và giúp người dùng có thể tiếp tục tương tác với ứng dụng.
  • Cập nhật tiến trình của công việc: BackgroundWorker cung cấp sự kiện ProgressChanged, cho phép bạn cập nhật tiến trình của công việc trên giao diện người dùng một cách liên tục. Điều này giúp người dùng biết được tình trạng của công việc và tăng trải nghiệm người dùng.
  • Quản lý tác vụ dễ dàng: BackgroundWorker cung cấp các phương thức để quản lý tác vụ, cho phép bạn bắt đầu hoặc hủy bỏ tác vụ một cách dễ dàng.
  • Dễ dàng tích hợp vào ứng dụng Windows Forms hoặc WPF: BackgroundWorker được thiết kế để tích hợp vào ứng dụng Windows Forms hoặc WPF một cách dễ dàng và linh hoạt.

3. Các properties trong background worker

Sau đây là một số thuộc tính thường được sử dụng và ví dụ:

  • WorkerReportsProgress: Get hoặc Set giá trị cho phép BackgroundWorker hỗ trợ báo cáo tiến trình. Các sự kiện được báo cáo tiến trình trong BackgroundWorker được sử dụng để thông báo tiến độ của một công việc nền tới UI thread. Khi thuộc tính WorkerReportsProgress được đặt là true, bạn có thể sử dụng phương thức ReportProgress để báo cáo tiến trình của công việc nền.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    int total = (int)e.Argument;

    for (int i = 0; i <= total; i++)
    {
        if (worker.CancellationPending)
        {
            e.Cancel = true;
            break;
        }
        else
        {
            // Báo cáo tiến trình cho người dùng
            int progressPercentage = (int)(((float)i / (float)total) * 100);
            worker.ReportProgress(progressPercentage);

            // Thực hiện công việc nền
            Thread.Sleep(100);
        }
    }
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // Cập nhật tiến trình của công việc nền cho người dùng
    progressBar1.Value = e.ProgressPercentage;
}

private void button1_Click(object sender, EventArgs e)
{
    // Khởi động BackgroundWorker để thực hiện công việc nền
    backgroundWorker1.WorkerReportsProgress = true;
    backgroundWorker1.RunWorkerAsync(100);
}

  • WorkerSupportsCancellation: Thuộc tính WorkerSupportsCancellation trong BackgroundWorker được sử dụng để cho phép người dùng hủy một công việc nền đang thực thi. Khi thuộc tính này được đặt là true, bạn có thể sử dụng phương thức CancelAsync để hủy thực hiện một công việc nền.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    int total = (int)e.Argument;

    for (int i = 0; i <= total; i++)
    {
        if (worker.CancellationPending)
        {
            e.Cancel = true;
            break;
        }
        else
        {
            // Thực hiện công việc nền
            Thread.Sleep(100);
        }
    }
}

private void button1_Click(object sender, EventArgs e)
{
    // Khởi động BackgroundWorker để thực hiện công việc nền
    backgroundWorker1.WorkerSupportsCancellation = true;
    backgroundWorker1.RunWorkerAsync(100);
}

private void button2_Click(object sender, EventArgs e)
{
    // Hủy thực hiện công việc nền
    if (backgroundWorker1.IsBusy)
    {
        backgroundWorker1.CancelAsync();
    }
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        // Công việc nền đã bị hủy bởi người dùng
        MessageBox.Show("Công vi

  • CancellationPending: Get giá trị để kiểm tra xem người dùng đã yêu cầu hủy bỏ việc thực hiện một công việc nền hay chưa. Trả về boolean.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    int total = (int)e.Argument;

    for (int i = 0; i <= total; i++)
    {
        if (worker.CancellationPending)
        {
            e.Cancel = true;
            break;
        }
        else
        {
            // Thực hiện công việc nền
            Thread.Sleep(100);
            worker.ReportProgress(i * 100 / total);
        }
    }
}

private void button1_Click(object sender, EventArgs e)
{
    // Khởi động BackgroundWorker để thực hiện công việc nền
    backgroundWorker1.RunWorkerAsync(100);
}

private void button2_Click(object sender, EventArgs e)
{
    // Hủy thực hiện công việc nền
    if (backgroundWorker1.IsBusy)
    {
        backgroundWorker1.CancelAsync();
    }
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // Cập nhật trạng thái của công việc nền
    progressBar1.Value = e.ProgressPercentage;
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        // Công việc nền đã bị hủy bởi người dùng
        MessageBox.Show("Công việc nền đã bị hủy bởi người dùng.");
    }
    else if (e.Error != null)
    {
        // Xảy ra lỗi trong quá trình thực hiện công việc nền
        MessageBox.Show("Xảy ra lỗi trong quá trình thực hiện công việc nền: " + e.Error.Message);
    }
    else
    {
        // Công việc nền đã thực hiện xong
        MessageBox.Show("Công việc nền đã thực hiện xong.");
    }
}

  • IsBusy: Lấy giá trị cho biết BackgroundWorker có đang chạy một hoạt động nền hay không.
private BackgroundWorker _worker;

private void buttonStart_Click(object sender, EventArgs e)
{
    _worker = new BackgroundWorker();
    _worker.DoWork += Worker_DoWork;
    _worker.RunWorkerCompleted += Worker_RunWorkerCompleted;

    if (!_worker.IsBusy)
    {
        _worker.RunWorkerAsync();
        labelStatus.Text = "Worker started.";
    }
    else
    {
        labelStatus.Text = "Worker is already running.";
    }
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    // Perform a long-running operation here...
}

private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    labelStatus.Text = "Worker completed.";
}

  • DoWorkEventArgs: Lấy đối số được truyền vào cho xử lý sự kiện DoWork.
private BackgroundWorker _worker;

private void buttonStart_Click(object sender, EventArgs e)
{
    _worker = new BackgroundWorker();
    _worker.DoWork += Worker_DoWork;
    _worker.RunWorkerCompleted += Worker_RunWorkerCompleted;

    if (!_worker.IsBusy)
    {
        _worker.RunWorkerAsync(textBoxInput.Text);
        labelStatus.Text = "Worker started.";
    }
    else
    {
        labelStatus.Text = "Worker is already running.";
    }
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    string input = e.Argument as string;
    
    // Perform some operation with the input...

    e.Result = "Output";
}

private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
        labelStatus.Text = "Worker encountered an error: " + e.Error.Message;
    }
    else if (e.Cancelled)
    {
        labelStatus.Text = "Worker was cancelled.";
    }
    else
    {
        string output = e.Result as string;
        labelStatus.Text = "Worker completed with output: " + output;
    }
}

  • Error: Lấy lỗi nếu có bất kỳ lỗi nào xảy ra trong quá trình thực thi của hoạt động chạy nền (background).
private BackgroundWorker _worker;

private void buttonStart_Click(object sender, EventArgs e)
{
    _worker = new BackgroundWorker();
    _worker.DoWork += Worker_DoWork;
    _worker.RunWorkerCompleted += Worker_RunWorkerCompleted;

    if (!_worker.IsBusy)
    {
        _worker.RunWorkerAsync();
        labelStatus.Text = "Worker started.";
    }
    else
    {
        labelStatus.Text = "Worker is already running.";
    }
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        // Perform some operation that might throw an exception...
    }
    catch (Exception ex)
    {
        e.Result = null;
        e.Cancel = true;
        e.Error = ex;
    }
}

private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
        labelStatus.Text = "Worker encountered an error: " + e.Error.Message;
    }
    else if (e.Cancelled)
    {
        labelStatus.Text = "Worker was cancelled.";
    }
    else
    {
        // Do something with the result...
    }
}

  • Result: Lấy kết quả của hoạt động chạy nền (background).
private BackgroundWorker _worker;

private void buttonStart_Click(object sender, EventArgs e)
{
    _worker = new BackgroundWorker();
    _worker.DoWork += Worker_DoWork;
    _worker.RunWorkerCompleted += Worker_RunWorkerCompleted;

    if (!_worker.IsBusy)
    {
        _worker.RunWorkerAsync();
        labelStatus.Text = "Worker started.";
    }
    else
    {
        labelStatus.Text = "Worker is already running.";
    }
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    // Perform some long-running operation...
    e.Result = "Result of the operation";
}

private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
        labelStatus.Text = "Worker encountered an error: " + e.Error.Message;
    }
    else if (e.Cancelled)
    {
        labelStatus.Text = "Worker was cancelled.";
    }
    else
    {
        labelStatus.Text = "Worker completed with result: " + e.Result;
    }
}

4. Các phương thức (methods) trong background worker

  • CancelAsync(): Yêu cầu hủy bỏ công việc đang thực thi bằng cách đặt giá trị CancellationPending thành true.
  • ReportProgress(int percentProgress, object userState): Báo cáo tiến trình công việc đang thực thi và truyền dữ liệu bổ sung (nếu có) thông qua đối số userState.
  • RunWorkerAsync(object argument): Gửi yêu cầu bắt đầu thực thi công việc nền và truyền tham số (nếu có).
  • Dispose(): Giải phóng tài nguyên được sử dụng bởi BackgroundWorker.

Ngoài ra, BackgroundWorker cũng cung cấp các sự kiện để cho phép ứng dụng phản hồi khi thực hiện công việc đang chạy:

  • DoWork: Sự kiện xảy ra khi công việc bắt đầu thực thi.
  • ProgressChanged: Sự kiện xảy ra khi tiến trình của công việc được báo cáo.
  • RunWorkerCompleted: Sự kiện xảy ra khi công việc hoàn thành hoặc bị hủy.

5. Một vài use case sử dụng background worker

  • Download dữ liệu lớn từ Internet: Khi ứng dụng cần phải tải dữ liệu lớn từ internet, thì việc thực hiện trong chính luồng UI sẽ làm cho ứng dụng bị đơ và không phản hồi. Trong trường hợp này, có thể sử dụng Background Worker để tải dữ liệu trong nền, và cập nhật tiến độ tải dữ liệu qua ProgressChanged event.

  • Xử lý dữ liệu lớn: Khi ứng dụng cần phải xử lý dữ liệu lớn, ví dụ như chuyển đổi định dạng hình ảnh, thì việc thực hiện trong chính luồng UI sẽ làm cho ứng dụng bị đơ và không phản hồi. Trong trường hợp này, có thể sử dụng Background Worker để xử lý dữ liệu trong nền, và cập nhật tiến độ xử lý qua ProgressChanged event.

  • Gửi email: Khi ứng dụng cần phải gửi email, việc thực hiện trong chính luồng UI sẽ làm cho ứng dụng bị đơ và không phản hồi. Trong trường hợp này, có thể sử dụng Background Worker để gửi email trong nền, và cập nhật tiến độ gửi email qua ProgressChanged event.

  • Đọc dữ liệu từ file: Khi ứng dụng cần phải đọc dữ liệu từ file lớn, thì việc thực hiện trong chính luồng UI sẽ làm cho ứng dụng bị đơ và không phản hồi. Trong trường hợp này, có thể sử dụng Background Worker để đọc dữ liệu trong nền, và cập nhật tiến độ đọc dữ liệu qua ProgressChanged event.

  • Tính toán phức tạp: Khi ứng dụng cần phải thực hiện tính toán phức tạp, ví dụ như tính toán đường cong trong đồ họa, thì việc thực hiện trong chính luồng UI sẽ làm cho ứng dụng bị đơ và không phản hồi. Trong trường hợp này, có thể sử dụng Background Worker để tính toán trong nền, và cập nhật tiến độ tính toán qua ProgressChanged event.

Ví dụ về sử dụng background worker khi download dữ liệu từ internet:

private void btnDownload_Click(object sender, EventArgs e)
{
    if (!bgwDownload.IsBusy)
    {
        // Lấy URL từ TextBox
        string url = txtURL.Text;

        // Bắt đầu tiến trình
        bgwDownload.RunWorkerAsync(url);
    }
}

private void bgwDownload_DoWork(object sender, DoWorkEventArgs e)
{
    // Lấy URL từ tham số của tiến trình
    string url = e.Argument as string;

    // Sử dụng HttpClient để tải dữ liệu từ URL
    using (var client = new HttpClient())
    {
        var response = client.GetAsync(url).Result;

        // Nếu yêu cầu thành công, đọc dữ liệu từ phản hồi và trả về cho tiến trình
        if (response.IsSuccessStatusCode)
        {
            var content = response.Content.ReadAsStringAsync().Result;
            e.Result = content;
        }
        else
        {
            // Nếu yêu cầu không thành công, báo lỗi cho tiến trình
            throw new Exception("Lỗi tải dữ liệu từ mạng.");
        }
    }
}

private void bgwDownload_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Nếu tiến trình bị hủy, không cập nhật UI
    if (e.Cancelled)
    {
        return;
    }

    // Nếu tiến trình không có lỗi, hiển thị dữ liệu trong TextBox
    if (e.Error == null)
    {
        txtData.Text = e.Result as string;
    }
    else
    {
        // Nếu có lỗi, hiển thị thông báo cho người dùng
        MessageBox.Show(e.Error.Message);
    }
}

6. Tổng kết

  • Qua bài viết này hy vọng mọi người hiểu được cơ bản về background worker và cách sử dụng trong .NET.
  • Cảm ơn mọi người đã xem bài viết. Chúc anh em một ngày làm việc hiệu quả và đầy năng lượng. Nếu có thắc mắc về các phần trong bài này mọi người có thể inbox qua facebook:https://www.facebook.com/FriendsCode-108096425243996 Mình sẽ giải đáp thắc mắc trong tầm hiểu biết. Cảm ơn mọi người!
  • Hoặc liên hệ mình qua facebook cá nhân: https://www.facebook.com/Flamesofwars/

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí