BackgroundWorker가 취소 될 때까지 기다리는 방법은 무엇입니까?


125

당신을 위해 물건을 만드는 가상의 방법을 고려하십시오 :

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

    ...

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

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

BackgroundWorker가 완료 될 때까지 어떻게 기다릴 수 있습니까?


과거에는 사람들이 시도했습니다 :

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

그러나 이 교착 상태 때문에 IsBusy때까지 해제되지 않은 RunWorkerCompleted이벤트가 처리되며, 응용 프로그램이 유휴 상태가 될 때까지 해당 이벤트가 처리되지 수 있습니다. 작업자가 완료 될 때까지 응용 프로그램이 유휴 상태가되지 않습니다. (또한, 그것은 바쁜 루프입니다-역겨운.)

다른 사람들은 그것을 제안하는 것을 제안했습니다.

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

문제는 Application.DoEvents()현재 큐에있는 메시지를 처리하여 재진입 문제를 일으킨다는 것입니다 (.NET은 재진입 할 ​​수 없음).

코드 가 이벤트를 기다리는 이벤트 동기화 객체와 관련된 솔루션을 사용 하려고합니다. 워커의 RunWorkerCompleted이벤트 핸들러가 설정합니다. 다음과 같은 것 :

Event _workerDoneEvent = new WaitHandle();

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

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

그러나 교착 상태로 돌아 왔습니다. 응용 프로그램이 유휴 상태가 될 때까지 이벤트 처리기를 실행할 수 없으며 응용 프로그램이 이벤트를 대기 중이기 때문에 유휴 상태가 아닙니다.

그러면 BackgroundWorker가 완료 될 때까지 어떻게 기다릴 수 있습니까?


업데이트 사람들은이 질문에 의해 혼동 될 것으로 보인다. 그들은 내가 BackgroundWorker를 다음과 같이 사용할 것이라고 생각하는 것 같습니다.

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

없습니다 입니다, 그것을 하지 내가 뭘하는지, 그리고는 하지 여기에 요구되는 내용. 이 경우에는 백그라운드 작업자를 사용할 필요가 없습니다.

답변:


130

귀하의 요구 사항을 올바르게 이해하면 다음과 같이 할 수 있습니다 (코드 테스트되지 않았지만 일반적인 아이디어를 보여줍니다)

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
백그라운드 작업자가 취소하는 데 시간이 오래 걸리면 UI가 차단됩니다 (예 : 다시 페인트하지 않음). UI와의 상호 작용을 중지하려면 다음 중 하나를 사용하는 것이 좋습니다. stackoverflow.com/questions/123661/…
Joe

1
의사가 주문한 것을 +1 ... 취소 요청이 1 초 이상 걸릴 수 있으면 @Joe에 동의하지만.
dotjoe

WaitOne 전에 CancelAsync를 처리하면 어떻게됩니까? 또는 취소 버튼이 한 번만 작동합니까?
CodingBarfield

6
((BackgroundWorker)sender).CancellationPending취소 이벤트를
받으려면를

1
Luuk에서 언급했듯이 확인 해야하는 Cancel 속성은 아니지만 CancellationPending입니다. 둘째, Joel Coehoorn이 언급했듯이 스레드가 종료되기를 기다리면 스레드를 처음 사용하는 목적이 무너집니다.
Captain Sensible

15

응답에 문제가 있습니다. 대기 중 UI는 메시지를 계속 처리해야합니다. 그렇지 않으면 다시 페인트하지 않습니다. 이는 백그라운드 작업자가 취소 요청에 응답하는 데 시간이 오래 걸리는 경우 문제가됩니다.

두 번째 결함은 _resetEvent.Set()작업자 스레드가 예외를 던지면 호출되지 않습니다. 메인 스레드가 무기한 대기 상태로 남아 있지만이 결함은 try / finally 블록으로 쉽게 수정할 수 있습니다.

이를 수행하는 한 가지 방법은 백그라운드 작업자가 작업을 완료했는지 (또는 사용자의 경우 취소를 완료했는지) 반복적으로 확인하는 타이머가있는 모달 대화 상자를 표시하는 것입니다. 백그라운드 워커가 완료되면 모달 대화 상자가 컨트롤을 응용 프로그램에 반환합니다. 이 문제가 발생할 때까지 사용자는 UI와 상호 작용할 수 없습니다.

다른 방법은 (모달리스 창을 최대 하나 열었다 고 가정) ActiveForm.Enabled = false를 설정 한 다음 백그라운드 작업자가 취소를 완료 할 때까지 Application, DoEvents를 반복 한 후 ActiveForm.Enabled = true를 다시 설정할 수 있습니다.


5
문제가 될 수 있지만 근본적으로 " BackgroundWorker가 취소 될 때까지 기다리는 방법"이라는 질문의 일부로 받아 들여집니다 . 기다리는 것은 당신이 기다린다는 것을 의미합니다. 여기에는 메시지 처리도 포함됩니다. 백그라운드 작업자를 기다리지 않으려면 .CancelAsync를 호출하면됩니다. 그러나 이것이 여기의 디자인 요구 사항은 아닙니다.
Ian Boyd

1
백그라운드 작업자를 기다리는 구절 인 CancelAsync 메서드의 값을 지적하면 +1입니다 .
Ian Boyd

10

거의 모든 사람들이이 질문에 혼란을 겪고 있으며 노동자가 어떻게 사용되는지 이해하고 있지 않습니다.

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;
}

그리고 모두 좋다.

이제 발신자가 로켓의 비상 자체 ​​파괴를 실행해야하므로 카운트 다운을 중단해야하는 상황이 발생합니다.

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

    StartClaxon();
    SelfDestruct();
}

또한 카운트 다운을 수행하지 않고 로켓에 대한 액세스 게이트를 열어야하는 상황도 있습니다.

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

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

마지막으로 로켓에 연료를 공급해야하지만 카운트 다운 중에는 허용되지 않습니다.

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

    if (rocketOnPad)
        OpenFuelValves();
}

작업자가 취소를 기다릴 수있는 능력이 없으면 세 가지 방법을 모두 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();
}

이제 내 코드를 그렇게 작성할 수는 있지만 그렇게하지는 않을 것입니다. 상관 없어요.


18
WaitForWorkerToFinish 메서드는 어디에 있습니까? 완전한 소스 코드?
Kiquenet

4

RunWorkerCompletedEventHandler 에서 RunWorkerCompletedEventArgs 를 체크인 하여 상태를 확인할 수 있습니다 . 성공, 취소 또는 오류

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

업데이트 : 작업자가 이것을 사용하여 .CancelAsync ()를 호출했는지 확인하려면 다음을 수행하십시오.

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

2
작업자가 완료 될 때까지 CancelDoingStuff ()를 반환 할 수 있어야합니다. 실제로 어떻게 완료했는지 확인하는 것은 관심이 없습니다.
Ian Boyd

그런 다음 이벤트를 작성해야합니다. 실제로 BackgroundWorker와는 아무런 관련이 없으므로 이벤트를 구현하고 듣고 듣고 완료하면됩니다. 그리고 RunWorkerCompletedEventHandler는 완료되었을 때입니다. 다른 이벤트를 시작하십시오.
Seb Nilsson

4

당신 은하지 않습니다 전체의 배경 노동자 기다립니다. 그것은 별도의 스레드를 시작하려는 목적을 거의 능가합니다. 대신 메소드를 완료하고 완료에 따라 다른 코드로 코드를 이동해야합니다. 작업자는 작업이 완료되면 알려주고 나머지 코드를 호출합니다.

완료 되기를 기다리는 경우 WaitHandle을 제공하는 다른 스레딩 구성을 사용하십시오.


1
당신은 하나를 제안 할 수 있습니까? 백그라운드 워커는 BackgroundWorker 객체를 생성 한 스레드에 알림을 보낼 수있는 유일한 스레딩 구성 인 것 같습니다.
Ian Boyd

백그라운드 워커UI 구성입니다. 자체 코드에서 여전히 호출 방법을 알아야하는 이벤트 만 사용합니다. 그것에 대한 자신의 대리인을 만드는 것을 막을 수있는 것은 없습니다.
Joel Coehoorn 2018 년

3

BackgroundWorker.RunWorkerCompleted 이벤트에 연결할 수없는 이유는 무엇입니까? "백그라운드 작업이 완료되었거나 취소되었거나 예외가 발생하면 발생합니다."라는 콜백입니다.


1
DoesStuff 개체를 사용하는 사람이 취소를 요청했기 때문입니다. 개체가 사용하는 공유 리소스는 연결을 끊고 제거하고 처리하고 닫으려고합니다.이를 수행 할 수 있는지 확인해야합니다.
Ian Boyd

1

BackgroundWorker가 완료되기를 기다리는 이유를 이해하지 못합니다. 실제로 수업에 대한 동기와 정반대입니다.

그러나 worker.IsBusy를 호출하여 모든 메소드를 시작할 수 있으며 실행중인 경우 종료되도록 할 수 있습니다.


나쁜 단어 선택; 완료되기를 기다리는 것이 아닙니다.
Ian Boyd

1

루프에있는 동안 비동기 프로세스를 실행하는 동안 대기 할 배경 작업자가 필요하기 때문에 여기에 왔다고 말하고 싶습니다.

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

이것이 내가 솔루션을 검색하는 동안 끝났기 때문에 내가 공유 할 것이라고 생각했습니다. 또한, 이것은 스택 오버플로에 대한 첫 번째 게시물이므로 나쁜 점이나 비평가를 좋아하는 사람이라면! :)


0

흠 .. 당신의 질문이 옳지 않을 수도 있습니다.

백그라운드 워커는 자신의 'workermethod'( backgroundworker.doWork-event 를 처리하는 메소드 / 함수 / 서브 )가 완료 되면 WorkerCompleted 이벤트를 호출 하므로 BW가 여전히 실행 중인지 확인할 필요가 없습니다. 작업자를 중지하려면 'worker method'내의 취소 보류 속성을 확인하십시오 .


근로자가 CancellationPending 속성을 모니터링하고 가능한 빨리 종료해야한다는 것을 이해합니다. 그러나 백그라운드 작업자에게 취소를 요청한 외부 사람은 어떻게 완료 될 때까지 기다리나요?
Ian Boyd

WorkCompleted 이벤트는 계속 발생합니다.
Joel Coehoorn

"그러나 백그라운드 작업자에게 취소를 요청한 외부 사람은 어떻게 완료 될 때까지 기다리나요?"
Ian Boyd

WorkCompleted 이벤트가 시작될 때까지 대기하여 완료 될 때까지 기다립니다. 사용자가 GUI 전체에 클릭을 맞추지 못하게하려면 위의 답변 에서 @Joe가 제안한 솔루션 중 하나를 사용할 수 있습니다 (양식을 비활성화하거나 모달을 표시하십시오). 다시 말해, 시스템의 유휴 루프가 사용자를 기다리게합니다. 완료되면 (이벤트를 시작하여) 알려줍니다.
Geoff

0

BackgroundWorker객체 의 워크 플로는 기본적으로 RunWorkerCompleted정상적인 실행 및 사용자 취소 사용 사례 모두에 대한 이벤트 를 처리해야합니다 . 이것이 RunWorkerCompletedEventArgs.Cancelled 속성입니다. 이 존재하는 입니다. 기본적으로이를 올바르게 수행하려면 Cancel 메소드 자체가 비동기 메소드 인 것으로 간주해야합니다.

예를 들면 다음과 같습니다.

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;
        }
    }
}

당신이 경우 정말 출구로 방법을 싶지 않아, 나는 같은 플래그를 두는 게 좋을 것 AutoResetEvent유래에 BackgroundWorker다음 오버라이드 (override), OnRunWorkerCompleted플래그를 설정할 수 있습니다. 그래도 여전히 일종의 혼잡입니다. cancel 이벤트를 비동기 메서드처럼 처리하고 현재 RunWorkerCompleted처리기 에서 수행하는 작업을 수행하는 것이 좋습니다 .


RunWorkerCompleted로 코드를 이동하는 것은 코드가 속한 곳이 아니며 예쁘지 않습니다.
Ian Boyd

0

나는 파티에 조금 늦었습니다 (약 4 년) .UI를 잠그지 않고 바쁜 루프를 처리 할 수있는 비동기 스레드를 설정 한 다음 해당 스레드의 콜백을 BackgroundWorker가 취소했다는 확인을 받도록하십시오 ?

이 같은:

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
    }
}

본질적으로 이것이하는 일은 다른 스레드를 백그라운드에서 실행하여 바쁜 루프에서 기다렸다가 MyWorker완료 되었는지 확인하는 것 입니다. 일단 MyWorker취소가 끝나면 스레드가 종료되고 AsyncCallback성공적인 취소를 수행하는 데 필요한 모든 메소드를 실행하는 데 사용할 수 있습니다 . 유사 이벤트처럼 작동합니다. 이것은 UI 스레드와 별개이므로 MyWorker취소가 완료 되기를 기다리는 동안 UI를 잠그지 않습니다 . 당신의 의도가 실제로 잠금을 취소하고 취소를 기다리는 것이라면 이것은 쓸모가 없지만 기다리기를 원한다면 다른 프로세스를 시작할 수 있습니다.


0

나는 이것이 정말로 늦다는 것을 알고 있지만 (5 년) 당신이 찾고있는 것은 Thread와 SynchronizationContext 를 사용하는 것 입니다. 프레임 워크에서 자동으로 수행하지 않고 UI 호출을 "수동으로"마샬링해야합니다.

이를 통해 필요한 경우 기다릴 수있는 스레드를 사용할 수 있습니다.


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

질문이 뭐야? 따라서 질문 할 때 구체적이어야합니다
Alma Do

@ 이것은 질문이 아닙니다.
코드 L ღ ver

0

이 문제에 대한 Fredrik Kalseth의 해결책은 지금까지 내가 찾은 최고입니다. 다른 솔루션을 사용 Application.DoEvent()하면 문제가 발생하거나 작동하지 않을 수 있습니다. 그의 솔루션을 재사용 가능한 클래스로 캐스트하겠습니다. BackgroundWorker봉인되어 있지 않기 때문에 클래스를 파생시킬 수 있습니다.

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();
        }
    }
}

플래그와 적절한 잠금 기능을 사용 _resetEvent.WaitOne()하면 일부 작업이 시작된 경우에만 호출되고 그렇지 않으면 _resetEvent.Set();호출되지 않을 수 있습니다!

try-finally는 _resetEvent.Set();DoWork-handler에서 예외가 발생하더라도 호출 될 것입니다. 그렇지 않으면 전화를 걸 때 응용 프로그램이 영원히 정지 될 수 있습니다CancelSync !

우리는 이것을 다음과 같이 사용할 것입니다 :

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() + "%";
}

다음 RunWorkerCompleted과 같이 이벤트에 핸들러를 추가 할 수도 있습니다 .
     BackgroundWorker Class (Microsoft documentation) .


0

양식을 닫으면 열려있는 로그 파일이 닫힙니다. 내 백그라운드 작업자가 해당 로그 파일을 작성하므로MainWin_FormClosing() 백그라운드 작업자가 종료 될 때까지 완료 할 . 백그라운드 워커가 종료되기를 기다리지 않으면 예외가 발생합니다.

왜 이렇게 어려운가요?

간단한 Thread.Sleep(1500)작동하지만 종료가 지연되거나 (너무 긴 경우) 예외가 발생합니다 (너무 짧은 경우).

백그라운드 워커가 종료 된 직후 종료하려면 변수를 사용하십시오. 이것은 나를 위해 일하고 있습니다 :

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

RunWorkerCompleted 이벤트를 피기 백 할 수 있습니다. _worker에 대한 이벤트 핸들러를 이미 추가 했더라도 추가 된 순서대로 실행할 다른 핸들러를 추가 할 수 있습니다.

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();
    }
}

취소가 발생할 수있는 여러 가지 이유가있는 경우 단일 RunWorkerCompleted 처리기의 논리를 원하는 것보다 더 복잡하게 만들면 유용 할 수 있습니다. 예를 들어, 사용자가 양식을 닫으려고 할 때 취소 :

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

0

나는 async방법을 사용 await하고 작업자가 작업을 마칠 때까지 기다립니다.

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

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

그리고 DoWork방법에서 :

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

또한 캡슐화 수 while에 루프 DoWorktry ... catch세트가 _isBusy있다 false예외에. 또는 단순히 확인 _worker.IsBusy에서 StopAsyncwhile 루프.

전체 구현의 예는 다음과 같습니다.

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;
    }
}

작업자를 중지하고 끝까지 실행될 때까지 기다립니다.

await myBackgroundWorker.StopAsync();

이 방법의 문제점은 다음과 같습니다.

  1. 비동기 메소드를 사용해야합니다.
  2. await Task.Delay가 정확하지 않습니다. 내 PC에서 Task.Delay (1)는 실제로 ~ 20ms를 기다립니다.

-2

오,이 중 일부는 엄청나게 복잡해졌습니다. DoWork 핸들러 내에서 BackgroundWorker.CancellationPending 속성을 확인하기 만하면됩니다. 언제든지 확인할 수 있습니다. 보류중인 경우 e.Cancel = True로 설정하고 메소드에서 제외하십시오.

// 여기 메소드 private void Worker_DoWork (객체 발신자, DoWorkEventArgs e) {BackgroundWorker bw = (BackgroundWorker로 보냄);

// do stuff

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

// do other stuff

}


1
이 솔루션을 사용하면 CancelAsync라는 사람이 백그라운드 작업자가 취소 될 때까지 어떻게 대기해야합니까?
Ian Boyd
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.