Làm thế nào để chờ đợi BackgroundWorker hủy?


125

Hãy xem xét một phương pháp giả định của một đối tượng thực hiện công cụ cho bạn:

public class DoesStuff
{
    BackgroundWorker _worker = new BackgroundWorker();

    ...

    public void CancelDoingStuff()
    {
        _worker.CancelAsync();

        //todo: Figure out a way to wait for BackgroundWorker to be cancelled.
    }
}

Làm thế nào người ta có thể chờ đợi một BackgroundWorker được thực hiện?


Trong quá khứ mọi người đã thử:

while (_worker.IsBusy)
{
    Sleep(100);
}

Nhưng sự bế tắc này , vì IsBusykhông được xóa cho đến khi RunWorkerCompletedsự kiện được xử lý và sự kiện đó không thể được xử lý cho đến khi ứng dụng không hoạt động. Ứng dụng sẽ không hoạt động cho đến khi nhân viên hoàn thành. (Thêm vào đó, đó là một vòng lặp bận rộn - kinh tởm.)

Những người khác đã thêm đề xuất loại bỏ nó vào:

while (_worker.IsBusy)
{
    Application.DoEvents();
}

Vấn đề với điều đó là Application.DoEvents()nguyên nhân khiến các thông báo hiện tại trong hàng đợi được xử lý, gây ra sự cố nhập lại (.NET không được đăng ký lại).

Tôi hy vọng sẽ sử dụng một số giải pháp liên quan đến các đối tượng đồng bộ hóa Sự kiện, trong đó mã chờ một sự kiện - mà RunWorkerCompletedbộ xử lý sự kiện của công nhân đặt. Cái gì đó như:

Event _workerDoneEvent = new WaitHandle();

public void CancelDoingStuff()
{
    _worker.CancelAsync();
    _workerDoneEvent.WaitOne();
}

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    _workerDoneEvent.SetEvent();
}

Nhưng tôi trở lại bế tắc: trình xử lý sự kiện không thể chạy cho đến khi ứng dụng không hoạt động và ứng dụng sẽ không hoạt động vì nó đang chờ một Sự kiện.

Vì vậy, làm thế nào bạn có thể chờ đợi một BackgroundWorker kết thúc?


Cập nhật Mọi người dường như bị nhầm lẫn bởi câu hỏi này. Họ dường như nghĩ rằng tôi sẽ sử dụng BackgroundWorker như:

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);

Đó không phải là nó, đó không phải là những gì tôi đang làm, và đó không phải là những gì đang được hỏi ở đây. Nếu đó là trường hợp, sẽ không có điểm nào trong việc sử dụng một nhân viên nền.

Câu trả lời:


130

Nếu tôi hiểu đúng yêu cầu của bạn, bạn có thể làm một cái gì đó như thế này (mã không được kiểm tra, nhưng hiển thị ý tưởng chung):

private BackgroundWorker worker = new BackgroundWorker();
private AutoResetEvent _resetEvent = new AutoResetEvent(false);

public Form1()
{
    InitializeComponent();

    worker.DoWork += worker_DoWork;
}

public void Cancel()
{
    worker.CancelAsync();
    _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    while(!e.Cancel)
    {
        // do something
    }

    _resetEvent.Set(); // signal that worker is done
}

7
Điều này sẽ chặn UI (ví dụ: không lặp lại) nếu nhân viên nền mất nhiều thời gian để hủy. Tốt hơn hết là sử dụng một trong những cách sau để ngừng tương tác với UI: stackoverflow.com/questions/123661/ mẹo
Joe

1
+1 chỉ những gì bác sĩ đã yêu cầu ... mặc dù tôi đồng ý với @Joe nếu yêu cầu hủy có thể mất hơn một giây.
dotjoe

Điều gì xảy ra khi CancAsync được xử lý trước WaitOne? Hoặc là nút Hủy chỉ hoạt động một lần.
CodingBarfield

6
Tôi cần kiểm tra ((BackgroundWorker)sender).CancellationPendingđể có được sự kiện hủy
Luuk

1
Như Luuk đã đề cập, đây không phải là tài sản Hủy phải được kiểm tra mà là Hủy bỏ. Thứ hai, như Joel Coehoorn đề cập, chờ đợi chuỗi kết thúc đánh bại mục đích sử dụng một luồng ở vị trí đầu tiên.
Thuyền trưởng Sensible

15

Có một vấn đề với điều này phản ứng . Giao diện người dùng cần tiếp tục xử lý tin nhắn trong khi bạn chờ đợi, nếu không nó sẽ không được sơn lại, đây sẽ là một vấn đề nếu nhân viên nền của bạn mất nhiều thời gian để trả lời yêu cầu hủy.

Một lỗ hổng thứ hai là _resetEvent.Set() sẽ không bao giờ được gọi nếu luồng công nhân ném một ngoại lệ - khiến cho luồng chính chờ đợi vô thời hạn - tuy nhiên lỗ hổng này có thể dễ dàng được sửa chữa bằng một khối thử / cuối cùng.

Một cách để làm điều này là hiển thị hộp thoại theo chế độ có bộ hẹn giờ liên tục kiểm tra xem nhân viên nền đã hoàn thành công việc chưa (hoặc hủy xong trong trường hợp của bạn). Khi nhân viên nền đã kết thúc, hộp thoại phương thức trả lại quyền điều khiển cho ứng dụng của bạn. Người dùng không thể tương tác với UI cho đến khi điều này xảy ra.

Một phương pháp khác (giả sử bạn mở tối đa một cửa sổ không mod) là đặt ActiveForm.Enables = false, sau đó lặp lại trên Application, DoEvents cho đến khi nhân viên nền đã hủy xong, sau đó bạn có thể thiết lập lại ActiveForm.Enables = true.


5
Đó có thể là một vấn đề, nhưng về cơ bản nó được chấp nhận như là một phần của câu hỏi, "Làm thế nào để chờ đợi BackgroundWorker hủy bỏ". Chờ đợi có nghĩa là bạn chờ đợi, bạn không làm gì khác. Điều này cũng bao gồm xử lý tin nhắn. Nếu tôi không muốn đợi nhân viên nền, bạn có thể gọi. HủyAsync. Nhưng đó không phải là yêu cầu thiết kế ở đây.
Ian Boyd

1
+1 để chỉ ra giá trị của phương thức CancAsync, các câu thơ đang chờ nhân viên nền.
Ian Boyd

10

Hầu như tất cả các bạn đều bối rối trước câu hỏi và không hiểu cách sử dụng một công nhân.

Hãy xem xét một trình xử lý sự kiện RunWorkerComplete:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;
}

Và tất cả đều tốt.

Bây giờ đến một tình huống mà người gọi cần hủy bỏ đếm ngược vì họ cần thực hiện một vụ tự hủy khẩn cấp của tên lửa.

private void BlowUpRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    StartClaxon();
    SelfDestruct();
}

Và cũng có một tình huống chúng ta cần mở cổng truy cập vào tên lửa, nhưng không phải trong khi đếm ngược:

private void OpenAccessGates()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

Và cuối cùng, chúng ta cần khử nhiên liệu cho tên lửa, nhưng điều đó không được phép trong quá trình đếm ngược:

private void DrainRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Không có khả năng chờ đợi một công nhân hủy bỏ, chúng ta phải chuyển cả ba phương thức sang RunWorkerCompletedEvent:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;

    if (delayedBlowUpRocket)
        BlowUpRocket();
    else if (delayedOpenAccessGates)
        OpenAccessGates();
    else if (delayedDrainRocket)
        DrainRocket();
}

private void BlowUpRocket()
{
    if (worker != null)
    {
        delayedBlowUpRocket = true;
        worker.CancelAsync();
        return;
    }

    StartClaxon();
    SelfDestruct();
}

private void OpenAccessGates()
{
    if (worker != null)
    {
        delayedOpenAccessGates = true;
        worker.CancelAsync();
        return;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

private void DrainRocket()
{
    if (worker != null)
    {
        delayedDrainRocket = true;
        worker.CancelAsync();
        return;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Bây giờ tôi có thể viết mã của mình như thế, nhưng tôi sẽ không làm thế. Tôi không quan tâm, tôi chỉ là không.


18
Phương thức WaitForWorkerToFinish ở đâu? bất kỳ mã nguồn đầy đủ?
Kiquenet

4

Bạn có thể kiểm tra RunWorkerCompletedEventArss trong RunWorkerCompletedEventHandler để xem trạng thái là gì. Thành công, hủy bỏ hoặc một lỗi.

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    if(e.Cancelled)
    {
        Console.WriteLine("The worker was cancelled.");
    }
}

Cập nhật : Để xem nhân viên của bạn đã gọi. HủyAsync () bằng cách sử dụng:

if (_worker.CancellationPending)
{
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}

2
Yêu cầu là CancellingStuff () không thể quay lại cho đến khi công nhân hoàn thành. Kiểm tra làm thế nào nó hoàn thành thực sự không quan tâm.
Ian Boyd

Sau đó, bạn sẽ phải tạo ra một sự kiện. Nó thực sự không có gì để làm với BackgroundWorker, bạn chỉ cần thực hiện một sự kiện, lắng nghe và kích hoạt khi hoàn thành. Và RunWorkerCompletedEventHandler là khi nó hoàn thành. Cháy một sự kiện khác.
Seb Nilsson

4

Bạn không đợi nhân viên nền hoàn thành. Điều đó khá nhiều đánh bại mục đích khởi chạy một chủ đề riêng biệt. Thay vào đó, bạn nên để phương thức của mình kết thúc và di chuyển bất kỳ mã nào phụ thuộc vào việc hoàn thành đến một nơi khác. Bạn để nhân viên cho bạn biết khi nào xong và gọi bất kỳ mã nào còn lại sau đó.

Nếu bạn muốn đợi một cái gì đó hoàn thành, hãy sử dụng một cấu trúc luồng khác nhau cung cấp WaitHandle.


1
Bạn có thể đề nghị một? Một công nhân nền dường như là cấu trúc luồng duy nhất có thể gửi thông báo đến luồng đã xây dựng đối tượng BackgroundWorker.
Ian Boyd

Trình nền là một cấu trúc UI . Nó chỉ sử dụng một sự kiện mà mã của riêng bạn vẫn cần biết cách gọi. Không có gì ngăn cản bạn tạo ra các đại biểu của riêng bạn cho điều đó.
Joel Coehoorn

3

Tại sao bạn không thể liên kết với sự kiện BackgroundWorker.RunWorkerCompleted. Đó là một cuộc gọi lại sẽ "Xảy ra khi hoạt động nền đã hoàn thành, đã bị hủy hoặc đã đưa ra một ngoại lệ."


1
Bởi vì người đang sử dụng đối tượng DoesStuff đã yêu cầu hủy bỏ nó. Các tài nguyên được chia sẻ mà đối tượng sử dụng sắp bị ngắt kết nối, loại bỏ, xử lý, đóng và cần biết rằng nó đã được thực hiện để tôi có thể tiếp tục.
Ian Boyd

1

Tôi không hiểu lý do tại sao bạn muốn đợi một BackgroundWorker hoàn thành; nó thực sự có vẻ như trái ngược hoàn toàn với động lực cho lớp học.

Tuy nhiên, bạn có thể bắt đầu mọi phương thức với một lệnh gọi tới worker.IsBusy và để chúng thoát nếu nó đang chạy.


Lựa chọn từ ngữ xấu; Tôi không chờ đợi nó hoàn thành.
Ian Boyd

1

Chỉ muốn nói rằng tôi đến đây vì tôi cần một nhân viên nền để chờ trong khi tôi đang chạy một quy trình không đồng bộ trong khi trong một vòng lặp, cách khắc phục của tôi dễ dàng hơn tất cả những thứ khác ^^

foreach(DataRow rw in dt.Rows)
{
     //loop code
     while(!backgroundWorker1.IsBusy)
     {
         backgroundWorker1.RunWorkerAsync();
     }
}

Chỉ cần tìm tôi muốn chia sẻ bởi vì đây là nơi tôi đã kết thúc trong khi tìm kiếm một giải pháp. Ngoài ra, đây là bài viết đầu tiên của tôi về stack overflow vì vậy nếu nó xấu hoặc bất cứ điều gì tôi yêu thích các nhà phê bình! :)


0

Có lẽ tôi không hiểu câu hỏi của bạn.

Trình xử lý nền gọi sự kiện WorkerCompleted sau khi 'workermethod' (phương thức / hàm / phụ xử lý sự kiện backgroundworker.doWork ) kết thúc, do đó không cần kiểm tra xem BW có còn chạy không. Nếu bạn muốn dừng nhân viên của mình, hãy kiểm tra tài sản đang chờ hủy trong 'phương thức worker' của bạn.


tôi hiểu rằng nhân viên phải giám sát tài sản CancellingPending và thoát ra càng sớm càng tốt. Nhưng làm thế nào để người bên ngoài, người đã yêu cầu nhân viên nền hủy bỏ, chờ đợi nó được thực hiện?
Ian Boyd

Sự kiện WorkCompleted vẫn sẽ cháy.
Joel Coehoorn

"Nhưng làm thế nào mà người bên ngoài, người đã yêu cầu nhân viên nền hủy bỏ, chờ đợi nó được thực hiện?"
Ian Boyd

Bạn chờ đợi nó được thực hiện bằng cách chờ đợi sự kiện WorkCompleted khởi động. Nếu bạn muốn ngăn người dùng có một nhấp chuột phù hợp trên tất cả GUI của bạn thì bạn có thể sử dụng một trong những giải pháp được đề xuất bởi @Joe trong câu trả lời của anh ta ở trên (vô hiệu hóa biểu mẫu hoặc hiển thị một cái gì đó phương thức). Nói cách khác, hãy để vòng lặp nhàn rỗi của hệ thống chờ bạn; nó sẽ cho bạn biết khi nào nó kết thúc (bằng cách bắn sự kiện).
Geoff

0

Quy trình làm việc của một BackgroundWorkerđối tượng về cơ bản yêu cầu bạn xử lý RunWorkerCompletedsự kiện cho cả trường hợp sử dụng hủy bỏ thực thi thông thường và người dùng. Đây là lý do tại sao tài sản RunWorkerCompletedEventArss.Cancelling tồn tại. Về cơ bản, làm điều này đúng cách đòi hỏi bạn phải coi phương thức Hủy bỏ của mình là một phương thức không đồng bộ.

Đây là một ví dụ:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;

namespace WindowsFormsApplication1
{
    public class AsyncForm : Form
    {
        private Button _startButton;
        private Label _statusLabel;
        private Button _stopButton;
        private MyWorker _worker;

        public AsyncForm()
        {
            var layoutPanel = new TableLayoutPanel();
            layoutPanel.Dock = DockStyle.Fill;
            layoutPanel.ColumnStyles.Add(new ColumnStyle());
            layoutPanel.ColumnStyles.Add(new ColumnStyle());
            layoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
            layoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));

            _statusLabel = new Label();
            _statusLabel.Text = "Idle.";
            layoutPanel.Controls.Add(_statusLabel, 0, 0);

            _startButton = new Button();
            _startButton.Text = "Start";
            _startButton.Click += HandleStartButton;
            layoutPanel.Controls.Add(_startButton, 0, 1);

            _stopButton = new Button();
            _stopButton.Enabled = false;
            _stopButton.Text = "Stop";
            _stopButton.Click += HandleStopButton;
            layoutPanel.Controls.Add(_stopButton, 1, 1);

            this.Controls.Add(layoutPanel);
        }

        private void HandleStartButton(object sender, EventArgs e)
        {
            _stopButton.Enabled = true;
            _startButton.Enabled = false;

            _worker = new MyWorker() { WorkerSupportsCancellation = true };
            _worker.RunWorkerCompleted += HandleWorkerCompleted;
            _worker.RunWorkerAsync();

            _statusLabel.Text = "Running...";
        }

        private void HandleStopButton(object sender, EventArgs e)
        {
            _worker.CancelAsync();
            _statusLabel.Text = "Cancelling...";
        }

        private void HandleWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                _statusLabel.Text = "Cancelled!";
            }
            else
            {
                _statusLabel.Text = "Completed.";
            }

            _stopButton.Enabled = false;
            _startButton.Enabled = true;
        }

    }

    public class MyWorker : BackgroundWorker
    {
        protected override void OnDoWork(DoWorkEventArgs e)
        {
            base.OnDoWork(e);

            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(500);

                if (this.CancellationPending)
                {
                    e.Cancel = true;
                    e.Result = false;
                    return;
                }
            }

            e.Result = true;
        }
    }
}

Nếu bạn thực sự không muốn phương thức của mình thoát ra, tôi khuyên bạn nên đặt một cờ giống như AutoResetEventtrên một dẫn xuất BackgroundWorker, sau đó ghi đè OnRunWorkerCompletedđể đặt cờ. Mặc dù vậy, nó vẫn là một loại bùn; Tôi khuyên bạn nên xử lý sự kiện hủy như một phương thức không đồng bộ và làm bất cứ điều gì hiện đang làm trong RunWorkerCompletedtrình xử lý.


Di chuyển mã sang RunWorkerCompleted không phải là nơi nó thuộc về và không đẹp.
Ian Boyd

0

Tôi đến bữa tiệc muộn một chút (khoảng 4 năm) nhưng về việc thiết lập một luồng không đồng bộ có thể xử lý một vòng lặp bận rộn mà không khóa UI, thì cuộc gọi lại từ luồng đó là xác nhận rằng BackgroundWorker đã hủy hoàn tất ?

Một cái gì đó như thế này:

class Test : Form
{
    private BackgroundWorker MyWorker = new BackgroundWorker();

    public Test() {
        MyWorker.DoWork += new DoWorkEventHandler(MyWorker_DoWork);
    }

    void MyWorker_DoWork(object sender, DoWorkEventArgs e) {
        for (int i = 0; i < 100; i++) {
            //Do stuff here
            System.Threading.Thread.Sleep((new Random()).Next(0, 1000));  //WARN: Artificial latency here
            if (MyWorker.CancellationPending) { return; } //Bail out if MyWorker is cancelled
        }
    }

    public void CancelWorker() {
        if (MyWorker != null && MyWorker.IsBusy) {
            MyWorker.CancelAsync();
            System.Threading.ThreadStart WaitThread = new System.Threading.ThreadStart(delegate() {
                while (MyWorker.IsBusy) {
                    System.Threading.Thread.Sleep(100);
                }
            });
            WaitThread.BeginInvoke(a => {
                Invoke((MethodInvoker)delegate() { //Invoke your StuffAfterCancellation call back onto the UI thread
                    StuffAfterCancellation();
                });
            }, null);
        } else {
            StuffAfterCancellation();
        }
    }

    private void StuffAfterCancellation() {
        //Things to do after MyWorker is cancelled
    }
}

Về bản chất, những gì nó làm là bắn ra một luồng khác để chạy trong nền mà chỉ chờ trong vòng lặp bận rộn của nó để xem nếu nó MyWorkerđã hoàn thành. Sau khi MyWorkerhủy xong, luồng sẽ thoát và chúng ta có thể sử dụng nó AsyncCallbackđể thực thi bất kỳ phương thức nào chúng ta cần để thực hiện hủy bỏ thành công - nó sẽ hoạt động giống như một sự kiện psuedo. Vì nó tách biệt với luồng UI nên nó sẽ không khóa UI trong khi chúng tôi chờ MyWorkerhoàn thành việc hủy. Nếu ý định của bạn thực sự là khóa và chờ hủy thì điều này là vô ích đối với bạn, nhưng nếu bạn chỉ muốn đợi để bạn có thể bắt đầu một quy trình khác thì điều này sẽ hoạt động tốt.


0

Tôi biết điều này thực sự muộn (5 năm) nhưng điều bạn đang tìm kiếm là sử dụng một Thread và SyncizationContext . Bạn sẽ phải sắp xếp các cuộc gọi UI trở lại chủ đề UI "bằng tay" thay vì để Framework thực hiện tự động một cách kỳ diệu.

Điều này cho phép bạn sử dụng một Chủ đề mà bạn có thể Chờ nếu cần.


0
Imports System.Net
Imports System.IO
Imports System.Text

Public Class Form1
   Dim f As New Windows.Forms.Form
  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
   BackgroundWorker1.WorkerReportsProgress = True
    BackgroundWorker1.RunWorkerAsync()
    Dim l As New Label
    l.Text = "Please Wait"
    f.Controls.Add(l)
    l.Dock = DockStyle.Fill
    f.StartPosition = FormStartPosition.CenterScreen
    f.FormBorderStyle = Windows.Forms.FormBorderStyle.None
    While BackgroundWorker1.IsBusy
        f.ShowDialog()
    End While
End Sub




Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Dim i As Integer
    For i = 1 To 5
        Threading.Thread.Sleep(5000)
        BackgroundWorker1.ReportProgress((i / 5) * 100)
    Next
End Sub

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    Me.Text = e.ProgressPercentage

End Sub

 Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

    f.Close()

End Sub

End Class

Câu hỏi của bạn là gì? Trong SO bạn phải cụ thể khi đặt câu hỏi
Alma Do

@ Đây là câu trả lời không phải là một câu hỏi.
Mã L ღ ver

0

Giải pháp của Fredrik Kalseth cho vấn đề này là cách tốt nhất tôi tìm thấy cho đến nay. Các giải pháp khác sử dụng Application.DoEvent()có thể gây ra vấn đề hoặc đơn giản là không hoạt động. Hãy để tôi đưa giải pháp của mình vào một lớp có thể tái sử dụng. Vì BackgroundWorkerkhông được niêm phong, chúng tôi có thể rút ra lớp của chúng tôi từ nó:

public class BackgroundWorkerEx : BackgroundWorker
{
    private AutoResetEvent _resetEvent = new AutoResetEvent(false);
    private bool _resetting, _started;
    private object _lockObject = new object();

    public void CancelSync()
    {
        bool doReset = false;
        lock (_lockObject) {
            if (_started && !_resetting) {
                _resetting = true;
                doReset = true;
            }
        }
        if (doReset) {
            CancelAsync();
            _resetEvent.WaitOne();
            lock (_lockObject) {
                _started = false;
                _resetting = false;
            }
        }
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        lock (_lockObject) {
            _resetting = false;
            _started = true;
            _resetEvent.Reset();
        }
        try {
            base.OnDoWork(e);
        } finally {
            _resetEvent.Set();
        }
    }
}

Với cờ và khóa thích hợp, chúng tôi đảm bảo rằng _resetEvent.WaitOne()thực sự chỉ được gọi nếu một số công việc đã được bắt đầu, nếu không _resetEvent.Set();có thể không bao giờ được gọi!

Thử cuối cùng đảm bảo rằng nó _resetEvent.Set();sẽ được gọi, ngay cả khi một ngoại lệ sẽ xảy ra trong trình xử lý DoWork của chúng tôi. Nếu không, ứng dụng có thể đóng băng mãi mãi khi gọiCancelSync !

Chúng tôi sẽ sử dụng nó như thế này:

BackgroundWorkerEx _worker;

void StartWork()
{
    StopWork();
    _worker = new BackgroundWorkerEx { 
        WorkerSupportsCancellation = true,
        WorkerReportsProgress = true
    };
    _worker.DoWork += Worker_DoWork;
    _worker.ProgressChanged += Worker_ProgressChanged;
}

void StopWork()
{
    if (_worker != null) {
        _worker.CancelSync(); // Use our new method.
    }
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    for (int i = 1; i <= 20; i++) {
        if (worker.CancellationPending) {
            e.Cancel = true;
            break;
        } else {
            // Simulate a time consuming operation.
            System.Threading.Thread.Sleep(500);
            worker.ReportProgress(5 * i);
        }
    }
}

private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressLabel.Text = e.ProgressPercentage.ToString() + "%";
}

Bạn cũng có thể thêm một trình xử lý cho RunWorkerCompletedsự kiện như được hiển thị ở đây:
     BackgroundWorker Class (tài liệu Microsoft) .


0

Đóng biểu mẫu sẽ đóng logfile mở của tôi. Nhân viên nền của tôi viết logfile đó, vì vậy tôi không thể đểMainWin_FormClosing() hoàn thành cho đến khi nhân viên nền của tôi chấm dứt. Nếu tôi không đợi nhân viên nền của mình chấm dứt, ngoại lệ sẽ xảy ra.

Tại sao nó lại khó thế này?

Một Thread.Sleep(1500)công việc đơn giản , nhưng nó trì hoãn tắt máy (nếu quá dài) hoặc gây ra ngoại lệ (nếu quá ngắn).

Để tắt ngay sau khi nhân viên nền kết thúc, chỉ cần sử dụng một biến. Điều này làm việc cho tôi:

private volatile bool bwRunning = false;

...

private void MainWin_FormClosing(Object sender, FormClosingEventArgs e)
{
    ... // Clean house as-needed.

    bwInstance.CancelAsync();  // Flag background worker to stop.
    while (bwRunning)
        Thread.Sleep(100);  // Wait for background worker to stop.
}  // (The form really gets closed now.)

...

private void bwBody(object sender, DoWorkEventArgs e)
{
    bwRunning = true;

    BackgroundWorker bw = sender as BackgroundWorker;

    ... // Set up (open logfile, etc.)

    for (; ; )  // infinite loop
    {
        ...
        if (bw.CancellationPending) break;
        ...
    } 

    ... // Tear down (close logfile, etc.)

    bwRunning = false;
}  // (bwInstance dies now.)

0

Bạn có thể cõng sự kiện RunWorkerCompleted. Ngay cả khi bạn đã thêm một trình xử lý sự kiện cho _worker, bạn có thể thêm một trình xử lý khác mà họ sẽ thực hiện theo thứ tự mà chúng đã được thêm vào.

public class DoesStuff
{
    BackgroundWorker _worker = new BackgroundWorker();

    ...

    public void CancelDoingStuff()
    {
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => 
        {
            // do whatever you want to do when the cancel completes in here!
        });
        _worker.CancelAsync();
    }
}

điều này có thể hữu ích nếu bạn có nhiều lý do tại sao việc hủy có thể xảy ra, làm cho logic của một trình xử lý RunWorkerCompleted trở nên phức tạp hơn bạn muốn. Chẳng hạn, hủy bỏ khi người dùng cố gắng đóng biểu mẫu:

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if (_worker != null)
    {
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => this.Close());
        _worker.CancelAsync();
        e.Cancel = true;
    }
}

0

Tôi sử dụng asyncphương pháp và awaitchờ nhân viên hoàn thành công việc:

    public async Task StopAsync()
    {
        _worker.CancelAsync();

        while (_isBusy)
            await Task.Delay(1);
    }

và trong DoWorkphương thức:

    public async Task DoWork()
    {
        _isBusy = true;
        while (!_worker.CancellationPending)
        {
            // Do something.
        }
        _isBusy = false;
    }

Bạn cũng có thể đóng gói các whilevòng trong DoWorkvới try ... catchđể thiết lập _isBusyfalsetrên ngoại lệ. Hoặc, chỉ cần kiểm tra _worker.IsBusytrong StopAsyncvòng lặp while.

Dưới đây là một ví dụ về thực hiện đầy đủ:

class MyBackgroundWorker
{
    private BackgroundWorker _worker;
    private bool _isBusy;

    public void Start()
    {
        if (_isBusy)
            throw new InvalidOperationException("Cannot start as a background worker is already running.");

        InitialiseWorker();
        _worker.RunWorkerAsync();
    }

    public async Task StopAsync()
    {
        if (!_isBusy)
            throw new InvalidOperationException("Cannot stop as there is no running background worker.");

        _worker.CancelAsync();

        while (_isBusy)
            await Task.Delay(1);

        _worker.Dispose();
    }

    private void InitialiseWorker()
    {
        _worker = new BackgroundWorker
        {
            WorkerSupportsCancellation = true
        };
        _worker.DoWork += WorkerDoWork;
    }

    private void WorkerDoWork(object sender, DoWorkEventArgs e)
    {
        _isBusy = true;
        try
        {
            while (!_worker.CancellationPending)
            {
                // Do something.
            }
        }
        catch
        {
            _isBusy = false;
            throw;
        }

        _isBusy = false;
    }
}

Để dừng công nhân và chờ cho đến khi nó chạy đến cuối:

await myBackgroundWorker.StopAsync();

Các vấn đề với phương pháp này là:

  1. Bạn phải sử dụng tất cả các phương thức async.
  2. đang chờ nhiệm vụ.Delay là không chính xác. Trên PC của tôi, Task.Delay (1) thực sự chờ ~ 20ms.

-2

Ôi trời, một vài trong số này đã trở nên phức tạp một cách lố bịch. tất cả những gì bạn cần làm là kiểm tra thuộc tính BackgroundWorker.CancnamePending bên trong trình xử lý DoWork. bạn có thể kiểm tra nó bất cứ lúc nào một khi nó đang chờ xử lý, hãy đặt e.Cattery = True và bảo lãnh từ phương thức.

// phương thức ở đây private void Worker_DoWork (người gửi đối tượng, DoWorkEventArss e) {BackgroundWorker bw = (người gửi dưới dạng BackgroundWorker);

// do stuff

if(bw.CancellationPending)
{
    e.Cancel = True;
    return;
}

// do other stuff

}


1
Và với giải pháp này, người đã gọi CancAsync buộc phải chờ nhân viên nền hủy như thế nào?
Ian Boyd
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.