developer tip

Form의 Closing 이벤트에서 BackgroundWorker를 중지하는 방법은 무엇입니까?

copycodes 2020. 11. 15. 11:26
반응형

Form의 Closing 이벤트에서 BackgroundWorker를 중지하는 방법은 무엇입니까?


BackgroundWorker를 생성하는 양식이 있으며 양식의 자체 텍스트 상자 (주 스레드에서)를 업데이트해야하므로 Invoke((Action) (...));호출합니다.
경우을에 HandleClosingEvent그냥 않는 bgWorker.CancelAsync()나는 얻을 ObjectDisposedExceptionInvoke(...)당연, 전화. 그러나 내가 앉아서 HandleClosingEventbgWorker가 끝날 때까지 기다리면 .Invoke (...)는 결코 반환되지 않습니다.

예외 또는 교착 상태가 발생하지 않고이 앱을 닫는 방법은 무엇입니까?

다음은 간단한 Form1 클래스의 세 가지 관련 메서드입니다.

    public Form1() {
        InitializeComponent();
        Closing += HandleClosingEvent;
        this.bgWorker.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
        while (!this.bgWorker.CancellationPending) {
            Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); }));
        }
    }

    private void HandleClosingEvent(object sender, CancelEventArgs e) {
        this.bgWorker.CancelAsync();
        /////// while (this.bgWorker.CancellationPending) {} // deadlock
    }

내가 아는 유일한 교착 상태 및 예외 안전 방법은 실제로 FormClosing 이벤트를 취소하는 것입니다. BGW가 아직 실행 중이면 e.Cancel = true로 설정하고 사용자가 닫기를 요청했음을 나타내는 플래그를 설정합니다. 그런 다음 BGW의 RunWorkerCompleted 이벤트 핸들러에서 해당 플래그를 확인하고 설정된 경우 Close ()를 호출합니다.

private bool closePending;

protected override void OnFormClosing(FormClosingEventArgs e) {
    if (backgroundWorker1.IsBusy) {
        closePending = true;
        backgroundWorker1.CancelAsync();
        e.Cancel = true;
        this.Enabled = false;   // or this.Hide()
        return;
    }
    base.OnFormClosing(e);
}

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    if (closePending) this.Close();
    closePending = false;
    // etc...
}

다른 방법을 찾았습니다. backgroundWorkers가 더 많은 경우 다음을 만들 수 있습니다.

List<Thread> bgWorkersThreads  = new List<Thread>();

그리고 모든 backgroundWorker의 DoWork 메서드에서 다음을 만듭니다.

bgWorkesThreads.Add(Thread.CurrentThread);

사용할 수있는 Arter :

foreach (Thread thread in this.bgWorkersThreads) 
{
     thread.Abort();    
}

컨트롤의 Word 추가 기능에서 사용했는데 CustomTaskPane. 누군가 문서 또는 응용 프로그램을 일찍 닫으면 모든 backgroundWorkes가 작업을 완료하면 일부가 발생합니다 COM Exception( 어떤 것이 정확히 기억 나지 않습니다). CancelAsync()작동하지 않습니다.

그러나 이것으로 backgroundworkers즉시 사용하는 모든 스레드를 닫을 수 있으며 DocumentBeforeClose문제가 해결됩니다.


여기 내 솔루션이 있습니다 (죄송합니다 .VB.Net에 있습니다).

FormClosing 이벤트를 실행할 때 BackgroundWorker1.CancelAsync ()를 실행하여 CancellationPending 값을 True로 설정합니다. 불행히도 프로그램은 e.Cancel 값을 true로 설정하기 위해 CancellationPending 값을 확인할 기회를 실제로 얻지 못합니다 (내가 말할 수있는 한 BackgroundWorker1_DoWork에서만 수행 할 수 있음). 실제로 차이가 나는 것 같지는 않지만 그 줄을 제거하지 않았습니다.

전역 변수 인 bClosingForm을 True로 설정하는 줄을 추가했습니다. 그런 다음 종료 단계를 수행하기 전에 BackgroundWorker_WorkCompleted에 코드 줄을 추가하여 e.Cancelled와 전역 변수 bClosingForm을 모두 확인했습니다.

이 템플릿을 사용하면 백그라운드 작업자가 어떤 작업의 중간에있는 경우에도 언제든지 양식을 닫을 수 있습니다 (좋지 않을 수 있지만 발생할 수 있으므로 처리 할 수 ​​있습니다). 필요한지 확실하지 않지만이 모든 작업이 수행 된 후 Form_Closed 이벤트에서 백그라운드 작업자를 완전히 처리 할 수 ​​있습니다.

Private bClosingForm As Boolean = False

Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
    bClosingForm = True
    BackgroundWorker1.CancelAsync() 
End Sub

Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    'Run background tasks:
    If BackgroundWorker1.CancellationPending Then
        e.Cancel = True
    Else
        'Background work here
    End If
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    If Not bClosingForm Then
        If Not e.Cancelled Then
            'Completion Work here
        End If
    End If
End Sub

양식의 소멸자에서 신호를 기다릴 수 없습니까?

AutoResetEvent workerDone = new AutoResetEvent();

private void HandleClosingEvent(object sender, CancelEventArgs e)
{
    this.bgWorker.CancelAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (!this.bgWorker.CancellationPending) {
        Invoke((Action) (() => { this.textBox1.Text =   
                                 Environment.TickCount.ToString(); }));
    }
}


private ~Form1()
{
    workerDone.WaitOne();
}


void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e )
{
    workerDone.Set();
}

첫째, ObjectDisposedException은 여기서 가능한 한 가지 함정일뿐입니다. OP의 코드를 실행하면 많은 경우에 다음과 같은 InvalidOperationException이 발생했습니다.

Invoke 또는 BeginInvoke는 창 핸들이 만들어 질 때까지 컨트롤에서 호출 할 수 없습니다.

생성자가 아닌 'Loaded'콜백에서 작업자를 시작하여 수정할 수 있다고 생각하지만 BackgroundWorker의 진행률보고 메커니즘을 사용하면 전체 시련을 피할 수 있습니다. 다음은 잘 작동합니다.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (!this.bgWorker.CancellationPending)
    {
        this.bgWorker.ReportProgress(Environment.TickCount);
        Thread.Sleep(1);
    }
}

private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.textBox1.Text = e.ProgressPercentage.ToString();
}

나는 백분율 매개 변수를 납치했지만 하나는 다른 과부하를 사용하여 매개 변수를 전달할 수 있습니다.

It is interesting to note that removing the above sleep call clogs the UI, consumes high CPU and continually increases the memory use. I guess it has something to do with the message queue of the GUI being overloaded. However, with the sleep call intact, the CPU usage is virtually 0 and the memory usage seems fine, too. To be prudent, perhaps a higher value than 1 ms should be used? An expert opinion here would be appreciated... Update: It appears that as long as the update isn't too frequent, it should be OK: Link

In any case, I can't foresee a scenario where the updating of the GUI has to be in intervals shorter than a couple of milliseconds (at least, in scenarios where a human is watching the GUI), so I think most of the time progress reporting would be the right choice


Your backgroundworker should not use Invoke to update the textbox. It should ask the UI thread nicely to update the textbox using event ProgressChanged with the value to put in the textbox attached.

During event Closed (or maybe event Closing), the UI thread remembers that the form is closed before it cancels the backgroundworker.

Upon receiving the progressChanged the UI thread checks if the form is closed and only if not, it updates the textbox.


This won't work for everyone, but if you are doing something in a BackgroundWorker periodically, like every second or every 10 seconds, (perhaps polling a server) this seems to work well to stop the process in an orderly manner and without error messages (at least so far) and is easy to follow;

 public void StopPoll()
        {
            MyBackgroundWorker.CancelAsync(); //Cancel background worker
            AutoResetEvent1.Set(); //Release delay so cancellation occurs soon
        }

 private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!MyBackgroundWorker.CancellationPending)
            {
            //Do some background stuff
            MyBackgroundWorker.ReportProgress(0, (object)SomeData);
            AutoResetEvent1.WaitOne(10000);
            }
    }

I really dont see why DoEvents is regarded as such a bad choice in this case if you are using this.enabled = false. I think it would make it quite neat.

protected override void OnFormClosing(FormClosingEventArgs e) {

    this.Enabled = false;   // or this.Hide()
    e.Cancel = true;
    backgroundWorker1.CancelAsync();  

    while (backgroundWorker1.IsBusy) {

        Application.DoEvents();

    }

    e.cancel = false;
    base.OnFormClosing(e);

}

I'd pass in the SynchronizationContext associated with the textbox to the BackgroundWorker and use that to perform Updates on the UI thread. Using SynchronizationContext.Post, you can check if the control is disposed or disposing.


What about Me.IsHandleCreated?

    Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted
    If Me.IsHandleCreated Then
        'Form is still open, so proceed
    End If
End Sub

Another way:

if (backgroundWorker.IsBusy)
{
    backgroundWorker.CancelAsync();
    while (backgroundWorker.IsBusy)
    {
        Application.DoEvents();
    }
}

One solution that works, but too complicated. The idea is to spawn the timer that will keep trying to close the form, and form will refuse to close until said bgWorker is dead.

private void HandleClosingEvent(object sender, CancelEventArgs e) {
    if (!this.bgWorker.IsBusy) {
        // bgWorker is dead, let Closing event proceed.
        e.Cancel = false;
        return;
    }
    if (!this.bgWorker.CancellationPending) {
        // it is first call to Closing, cancel the bgWorker.
        this.bgWorker.CancelAsync();
        this.timer1.Enabled = true;
    }
    // either this is first attempt to close the form, or bgWorker isn't dead.
    e.Cancel = true;
}

private void timer1_Tick(object sender, EventArgs e) {
    Trace.WriteLine("Trying to close...");
    Close();
}

참고URL : https://stackoverflow.com/questions/1731384/how-to-stop-backgroundworker-on-forms-closing-event

반응형