Bir BackgroundWorker'ın iptal etmesi için nasıl beklenir?


125

Sizin için bir şeyler yapan bir nesnenin varsayımsal bir yöntemini düşünü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.
    }
}

Bir BackgroundWorker'ın yapılması için nasıl beklenebilir?


Geçmişte insanlar denedi:

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

Ancak bu kilitlenir , çünkü olay IsBusyişlenene kadar temizlenmez RunWorkerCompletedve bu olay uygulama boşta kalana kadar işlenemez. İşçi bitene kadar uygulama boşta kalmayacaktır. (Ayrıca yoğun bir döngü - iğrenç.)

Diğerleri, içeriğin klud edilmesini önerdi:

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

Bununla ilgili sorun, Application.DoEvents()şu anda kuyrukta olan mesajların işlenmesine neden olarak yeniden giriş sorunlarına neden olmasıdır (.NET yeniden giriş değildir).

Kodun bulunduğu, Olay senkronizasyon nesnelerini içeren bir çözüm kullanmayı umuyorum. , çalışanın RunWorkerCompletedolay işleyicilerinin ayarladığı bir olayı beklediği . Gibi bir şey:

Event _workerDoneEvent = new WaitHandle();

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

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

Ancak çıkmaza geri döndüm: olay işleyicisi, uygulama boşta kalana kadar çalışamaz ve uygulama bir Olayı beklediği için boşta kalmaz.

Peki, bir BackgroundWorker'ın bitirmesini nasıl bekleyebilirsiniz?


Güncelleme İnsanların bu soruyla kafası karışmış görünüyor. BackgroundWorker'ı şu şekilde kullanacağımı düşünüyorlar:

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

Yani değil olduğunu, bu değil ben ne yaptığımı ve olmasıdır değil burada ne soruluyor. Durum böyle olsaydı, bir arka plan çalışanı kullanmanın bir anlamı olmazdı.

Yanıtlar:


130

Gereksiniminizi doğru anlarsam, şöyle bir şey yapabilirsiniz (kod test edilmemiştir, ancak genel fikri gösterir):

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
Bu, arka plan çalışanın iptal etmesi uzun sürerse, kullanıcı arayüzünü bloke eder (örneğin, yeniden boyama yapılmaz). Kullanıcı arayüzüyle etkileşimi durdurmak için aşağıdakilerden birini kullanmak daha iyidir: stackoverflow.com/questions/123661/…
Joe

1
Doktorun sipariş ettiği şeyi +1 ... yine de iptal isteği bir saniyeden fazla sürebilirse @ Joe ile aynı fikirdeyim.
dotjoe

CancelAsync WaitOne'dan önce işlendiğinde ne olur? Veya İptal düğmesi yalnızca bir kez çalışıyor mu?
CodingBarfield

6
((BackgroundWorker)sender).CancellationPendingİptal etkinliğini almak için kontrol etmem gerekiyordu
Luuk

1
Luuk tarafından belirtildiği gibi, kontrol edilmesi gereken İptal özelliği değil, İptali Beklemedir. İkincisi, Joel Coehoorn'un da belirttiği gibi, ipliğin sona ermesini beklemek, ilk etapta bir iplik kullanma amacını bozar.
Kaptan Duyarlı

15

Bu yanıtta bir sorun var . Kullanıcı arabiriminin siz beklerken mesajları işlemeye devam etmesi gerekir, aksi takdirde yeniden boyanmaz, bu da arka plan çalışanınızın iptal isteğine yanıt vermesi uzun sürerse sorun olur.

İkinci bir kusur şu ki _resetEvent.Set() işçi iş parçacığı bir istisna atarsa ​​- ana iş parçacığını sonsuza kadar beklettiğinde - asla çağrılmayacak olmasıdır, ancak bu kusur bir dene / son bloğu ile kolayca düzeltilebilir.

Bunu yapmanın bir yolu, arka plan çalışanın işi bitirip bitirmediğini (veya sizin durumunuzda iptal etmeyi bitirip bitirmediğini) tekrar tekrar kontrol eden bir zamanlayıcıya sahip kalıcı bir iletişim kutusu görüntülemektir. Arkaplan çalışanı bitirdiğinde, kalıcı iletişim kutusu uygulamanıza denetimi döndürür. Kullanıcı, bu gerçekleşene kadar UI ile etkileşim kuramaz.

Diğer bir yöntem (en fazla bir modelsiz pencereniz olduğunu varsayarak) ActiveForm.Enabled = false olarak ayarlamak, ardından arka plan çalışanı iptal etmeyi bitirene kadar Uygulama, DoEvents üzerinde döngü oluşturmaktır, ardından ActiveForm.Enabled = true değerini yeniden ayarlayabilirsiniz.


5
Bu bir sorun olabilir, ancak temelde " BackgroundWorker'ın iptal etmesi için nasıl beklenir " sorusunun bir parçası olarak kabul edilir . Beklemek, beklemek demektir, başka bir şey yapmazsın. Bu, mesajların işlenmesini de içerir. Arkaplan çalışanını beklemek istemiyorsam, .CancelAsync'i arayabilirsiniz. Ancak buradaki tasarım gerekliliği bu değildir.
Ian Boyd

1
CancelAsync yönteminin değerini göstermek için +1 , arka plan işçisini bekleyen ayetler .
Ian Boyd

10

Neredeyse hepiniz soruyla kafanız karıştı ve bir işçinin nasıl kullanıldığını anlamıyorsunuz.

Bir RunWorkerComplete olay işleyicisini düşünün:

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

Ve her şey yolunda.

Şimdi, arayanın geri sayımı iptal etmesi gereken bir durum geliyor çünkü roketin acil bir şekilde kendi kendini imha etmesini gerektiriyor.

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

    StartClaxon();
    SelfDestruct();
}

Ayrıca rokete erişim kapılarını açmamız gereken bir durum var, ancak geri sayım yaparken değil:

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

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

Son olarak, roketin yakıtını boşaltmamız gerekiyor, ancak geri sayım sırasında buna izin verilmez:

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

    if (rocketOnPad)
        OpenFuelValves();
}

Bir çalışanın iptal etmesini bekleme yeteneği olmadan, üç yöntemi de RunWorkerCompletedEvent'e taşımalıyız:

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

Şimdi kodumu böyle yazabilirim, ama yazmayacağım. Umrumda değil, sadece değilim.


18
WaitForWorkerToFinish yöntemi nerede? herhangi bir tam kaynak kodu?
Kiquenet

4

Durumun ne olduğunu görmek için RunWorkerCompletedEventHandler'daki RunWorkerCompletedEventArgs'ı kontrol edebilirsiniz . Başarılı, iptal edildi veya bir hata.

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

Güncelleme : Çalışanınızın bunu kullanarak .CancelAsync () 'i çağırıp çağırmadığını görmek için:

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

2
Ön koşul, çalışan tamamlanana kadar CancelDoingStuff () 'ın geri dönememesidir. Nasıl tamamlandığını kontrol etmek gerçekten ilgi çekici değil.
Ian Boyd

O zaman bir etkinlik oluşturmanız gerekecek. Özellikle BackgroundWorker ile hiçbir ilgisi yoktur, sadece bir olay uygulamanız, onu dinlemeniz ve bittiğinde ateş etmeniz gerekir. Ve bittiğinde RunWorkerCompletedEventHandler olur. Başka bir olay başlat.
Seb Nilsson

4

Sen değil komple arka plan işçi için bekleyin. Bu, ayrı bir iş parçacığı başlatma amacını büyük ölçüde bozar. Bunun yerine, yönteminizin bitmesine izin vermeli ve tamamlanmasına bağlı herhangi bir kodu farklı bir yere taşımalısınız. İşçinin bittiğinde size haber vermesine ve kalan kodu aramasına izin verirsiniz.

Bir şeyin tamamlanmasını beklemek istiyorsanız, WaitHandle sağlayan farklı bir iş parçacığı yapısı kullanın.


1
Bir tane önerebilir misin? Arka plan çalışanı, BackgroundWorker nesnesini oluşturan iş parçacığına bildirimler gönderebilen tek iş parçacığı yapısı gibi görünüyor.
Ian Boyd

Arka plan çalışanı bir UI yapısıdır. Sadece kendi kodunuzun nasıl çağırılacağını bilmesi gereken olayları kullanır. Bunun için kendi temsilcilerinizi yaratmanızı engelleyen hiçbir şey yok.
Joel Coehoorn

3

Neden BackgroundWorker.RunWorkerCompleted Etkinliğine bağlanamıyorsunuz? Bu, "Arka plan işlemi tamamlandığında, iptal edildiğinde veya bir istisna oluşturduğunda ortaya çıkacak" bir geri aramadır.


1
Çünkü DoesStuff nesnesini kullanan kişi onu iptal etmesini istedi. Nesnenin kullandığı paylaşılan kaynaklar bağlantısı kesilmek üzere, kaldırılıyor, elden çıkarılıyor, kapatılıyor ve devam edebilmem için bittiğini bilmesi gerekiyor.
Ian Boyd

1

Bir BackgroundWorker'ın tamamlanmasını neden beklemek istediğinizi anlamıyorum; gerçekten de sınıf için motivasyonun tam tersi gibi görünüyor.

Ancak, her yöntemi bir worker.IsBusy çağrısıyla başlatabilir ve çalışıyorsa çıkmalarını sağlayabilirsiniz.


Kötü kelime seçimi; tamamlanmasını beklemiyorum.
Ian Boyd

1

Buraya geldiğimi söylemek istiyorum çünkü bir döngüdeyken eşzamansız bir işlemi çalıştırırken beklemek için bir arka plan çalışanına ihtiyacım var, düzeltmem diğer tüm şeylerden çok daha kolaydı ^^

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

Paylaşacağımı düşündüm çünkü bir çözüm ararken sona erdiğim yer burasıydı. Ayrıca, bu yığın taşmasıyla ilgili ilk yazım, bu yüzden kötü ya da başka bir şey olursa eleştirmenleri severim! :)


0

Hm belki de sorunuzu doğru anlamıyorum.

Arka plan çalışanı WorkerCompleted olayını 'işçi yöntemi' ( backgroundworker.doWork olayını işleyen yöntem / işlev / alt ) tamamlandığında çağırır, böylece BW'nin hala çalışıp çalışmadığını kontrol etmeye gerek yoktur. Çalışanınızı durdurmak istiyorsanız , 'işçi yöntemi' içindeki iptal bekleyen mülkü kontrol edin .


İşçinin CancellationPending özelliğini izlemesi ve mümkün olan en kısa sürede çıkması gerektiğini anlıyorum. Fakat dışarıdan, arka plan çalışanı iptal talebinde bulunan kişi, bunun yapılmasını nasıl bekler?
Ian Boyd

WorkCompleted etkinliği yine de tetiklenecek.
Joel Coehoorn

"Ama dışarıdan, arka plandaki çalışanın iptalini talep eden kişi, bunun yapılmasını nasıl bekler?"
Ian Boyd

WorkCompleted olayının ateşlenmesini bekleyerek yapılmasını bekliyorsunuz. Kullanıcının GUI'nizin her yerine sığmasını önlemek istiyorsanız, @Joe tarafından önerilen çözümlerden birini yukarıdaki yanıtında kullanabilirsiniz (formu devre dışı bırakın veya kalıcı bir şey gösterin). Diğer bir deyişle, sistemin boşta kalma döngüsünün sizi beklemesine izin verin; ne zaman bittiğini size söyleyecektir (olayı ateşleyerek).
Geoff

0

Bir BackgroundWorkernesnenin iş akışı temelde RunWorkerCompletedolayı hem normal yürütme hem de kullanıcı iptali kullanım durumları için işlemenizi gerektirir . RunWorkerCompletedEventArgs.Cancelled özelliğinin var olmasının nedeni budur . Temel olarak, bunu doğru bir şekilde yapmak, İptal yönteminizin kendi başına bir zaman uyumsuz yöntem olduğunu düşünmenizi gerektirir.

İşte bir örnek:

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

Metodunuzun gerçekten çıkmasını istemiyorsanız, AutoResetEventtüretilmiş gibi bir bayrak koymanızı BackgroundWorker, ardından OnRunWorkerCompletedbayrağı ayarlamak için geçersiz kılmanızı öneririm . Yine de biraz karışık; Cancel olayını eşzamansız bir yöntem gibi ele almanızı ve şu anda RunWorkerCompletedişleyicide yaptığı şeyi yapmanızı öneririm .


Kodu RunWorkerCompleted'e taşımak, ait olduğu yer değildir ve hoş değildir.
Ian Boyd

0

Buradaki partiye biraz geç kaldım (yaklaşık 4 yıl), ancak UI'yi kilitlemeden meşgul bir döngüyü idare edebilen bir zaman uyumsuz iş parçacığı kurmaya ne dersiniz? ?

Bunun gibi bir şey:

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

Temelde bunun yaptığı şey, arka planda çalışacak başka bir iş parçacığının ateşlenmesidir ve bu işlemin MyWorkertamamlanıp tamamlanmadığını görmek için meşgul döngüsünde beklemektir . İş MyWorkerparçacığının iptali bittiğinde, iş parçacığı çıkacaktır ve AsyncCallbackbaşarılı iptali takip etmek için ihtiyacımız olan yöntemi yürütmek için kullanabiliriz - bir psuedo-olayı gibi çalışacaktır. Bu, UI iş parçacığından ayrı olduğundan, MyWorkeriptal işleminin tamamlanmasını beklerken UI'yi kilitlemeyecektir . Niyetiniz gerçekten kilitlemek ve iptali beklemekse, bu sizin için bir işe yaramaz, ancak başka bir işleme başlamak için beklemek istiyorsanız, bu iyi çalışır.


0

Bunun gerçekten geç olduğunu biliyorum (5 yıl) ama aradığınız şey bir Thread ve SynchronizationContext kullanmak . Framework'ün bunu otomatik olarak yapmasına izin vermek yerine UI çağrılarını "elle" UI iş parçacığına sıralamak zorunda kalacaksınız.

Bu, gerekirse Bekleyebileceğiniz bir İş Parçacığı kullanmanıza olanak tanır.


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

Sorunuz nedir? SO'da soru sorarken spesifik olmalısınız
Alma

@Bu cevap bir soru değil.
Code L ღ ver

0

Fredrik Kalseth'in bu soruna çözümü şu ana kadar bulduğum en iyisidir. Application.DoEvent()Sorunlara neden olabilecek veya işe yaramayan diğer çözümler kullanılır . Onun çözümünü yeniden kullanılabilir bir sınıfa dökmeme izin verin. Yana BackgroundWorkerkapalı değil, biz ondan sınıfımızı türetebilirsiniz:

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

Bayraklar ve uygun kilitlemeyle, _resetEvent.WaitOne()gerçekten yalnızca bazı çalışmalar başlatıldığında çağrılacağından emin oluyoruz , aksi takdirde _resetEvent.Set();asla aranmayabilir!

Try- _resetEvent.Set();final, DoWork işleyicimizde bir istisna meydana gelse bile bunun çağrılmasını sağlar . Aksi takdirde uygulama arama sırasında sonsuza kadar donabilir CancelSync!

Bunu şu şekilde kullanırdık:

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

RunWorkerCompletedBurada gösterildiği gibi olaya bir işleyici de ekleyebilirsiniz :
     BackgroundWorker Sınıfı (Microsoft belgeleri) .


0

Formu kapatmak, açık günlük dosyamı kapatır. Arka plan çalışanım bu günlük dosyasını yazar, bu nedenle MainWin_FormClosing()arka plan çalışanım sona erene kadar bitmesine izin veremem . Arka plan çalışanımın işini bitirmesini beklemiyorsam, istisnalar oluyor.

Bu neden bu kadar zor?

Basit Thread.Sleep(1500)çalışır, ancak kapatmayı geciktirir (çok uzunsa) veya istisnalara (çok kısaysa) neden olur.

Arka plan çalışanı sona erdikten hemen sonra kapatmak için bir değişken kullanın. Bu benim için çalışıyor:

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 olayından geri çekilebilirsiniz. _Worker için zaten bir olay işleyicisi eklemiş olsanız bile, eklendikleri sırayla yürütülecek başka bir olay işleyicisi ekleyebilirsiniz.

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

Bu, tek bir RunWorkerCompleted işleyicisinin mantığını istediğinizden daha karmaşık hale getirecek şekilde, bir iptalin meydana gelmesine neden olabilecek birden fazla nedeniniz varsa yararlı olabilir. Örneğin, bir kullanıcı formu kapatmaya çalıştığında iptal etme:

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

0

Kullandığım asyncyöntem ve awaitişini bitirme işçi için bekleyişin:

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

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

ve DoWorkyöntemde:

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

Ayrıca barındırabilir whileiçinde döngü DoWorkile try ... catchdizi için _isBusyolan falseistisna üzerinde. Veya, sadece kontrol _worker.IsBusyyılında StopAsyncise döngü.

İşte tam uygulama örneği:

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

İşçiyi durdurmak ve sonuna kadar çalışmasını beklemek için:

await myBackgroundWorker.StopAsync();

Bu yöntemle ilgili sorunlar şunlardır:

  1. Async yöntemlerini sonuna kadar kullanmalısınız.
  2. await Task.Delay yanlış. Bilgisayarımda Task.Delay (1) aslında ~ 20ms bekliyor.

-2

ah adamım, bunlardan bazıları gülünç derecede karmaşık hale geldi. Tüm yapmanız gereken DoWork işleyicisinin içindeki BackgroundWorker.CancellationPending özelliğini kontrol etmektir. istediğiniz zaman kontrol edebilirsiniz. beklemede olduğunda, e.Cancel = True olarak ayarlayın ve yöntemden kurtulun.

// yöntemi burada private void Worker_DoWork (nesne gönderen, DoWorkEventArgs e) {BackgroundWorker bw = (BackgroundWorker olarak gönderen);

// do stuff

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

// do other stuff

}


1
Ve bu çözümle, CancelAsync'i arayan kişi, arka plan çalışanının iptal etmesini beklemeye nasıl zorlanır?
Ian Boyd
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.