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

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

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

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



답변

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

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
}


답변

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

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

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

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


답변

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

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

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


답변

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


답변

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

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


답변

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


답변

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

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