Bagaimana cara menunggu BackgroundWorker dibatalkan?

125

Pertimbangkan metode hipotetis dari suatu objek yang melakukan hal-hal untuk Anda:

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

    ...

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

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

Bagaimana seseorang bisa menunggu BackgroundWorker dilakukan?


Di masa lalu orang telah mencoba:

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

Tapi ini kebuntuan , karena IsBusytidak dibersihkan sampai setelah RunWorkerCompletedacara ditangani, dan kejadian itu tidak bisa ditangani sampai aplikasi idle. Aplikasi tidak akan menganggur sampai pekerja selesai. (Plus, ini adalah lingkaran sibuk - menjijikkan.)

Yang lain menambahkan saran untuk memasukkannya ke:

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

Masalah dengan itu adalah yang Application.DoEvents()menyebabkan pesan saat ini dalam antrian untuk diproses, yang menyebabkan masalah re-entrancy (.NET tidak kembali masuk).

Saya berharap dapat menggunakan beberapa solusi yang melibatkan objek sinkronisasi acara, di mana kode menunggu sebuah acara - yang RunWorkerCompleteddiatur oleh penangan acara pekerja . Sesuatu seperti:

Event _workerDoneEvent = new WaitHandle();

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

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

Tapi saya kembali ke jalan buntu: event handler tidak bisa berjalan sampai aplikasi idle, dan aplikasi tidak akan idle karena menunggu Event.

Jadi bagaimana Anda bisa menunggu BackgroundWorker selesai?


Pembaruan Orang-orang tampaknya bingung oleh pertanyaan ini. Mereka tampaknya berpikir bahwa saya akan menggunakan BackgroundWorker sebagai:

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

Artinya tidak , itu adalah tidak apa yang saya lakukan, dan itu tidak apa yang diminta di sini. Jika itu masalahnya, tidak akan ada gunanya menggunakan pekerja latar belakang.

Ian Boyd
sumber

Jawaban:

130

Jika saya memahami kebutuhan Anda dengan benar, Anda dapat melakukan sesuatu seperti ini (kode tidak diuji, tetapi menunjukkan gagasan umum):

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
}
Fredrik Kalseth
sumber
7
Ini akan memblokir UI (mis. Tidak ada pengecatan ulang) jika pekerja latar membutuhkan waktu lama untuk membatalkan. Lebih baik menggunakan salah satu dari berikut ini untuk menghentikan interaksi dengan UI: stackoverflow.com/questions/123661/…
Joe
1
Memberi +1 hanya apa yang diperintahkan dokter ... walaupun saya setuju dengan @Joe jika permintaan pembatalan dapat memakan waktu lebih dari satu detik.
dotjoe
Apa yang terjadi ketika CancelAsync ditangani sebelum WaitOne? Atau apakah tombol Batal hanya berfungsi sekali.
CodingBarfield
6
Saya perlu memeriksa ((BackgroundWorker)sender).CancellationPendinguntuk mendapatkan pembatalan acara
Luuk
1
Seperti disebutkan oleh Luuk itu bukan properti Batal yang harus diperiksa tetapi Pembatalan. Kedua, seperti Joel Coehoorn menyebutkan, menunggu utas untuk mengakhiri mengalahkan tujuan menggunakan utas di tempat pertama.
Kapten Sensible
15

Ada masalah dengan respons ini . UI perlu terus memproses pesan saat Anda menunggu, jika tidak, tidak akan dicat ulang, yang akan menjadi masalah jika pekerja latar belakang Anda membutuhkan waktu lama untuk menanggapi permintaan pembatalan.

Celah kedua adalah yang _resetEvent.Set()tidak akan pernah dipanggil jika utas pekerja melemparkan pengecualian - meninggalkan utas utama menunggu tanpa batas waktu - namun cacat ini dapat dengan mudah diperbaiki dengan blok coba / akhirnya.

Salah satu cara untuk melakukan ini adalah dengan menampilkan dialog modal yang memiliki timer yang berulang kali memeriksa apakah pekerja latar belakang telah selesai bekerja (atau selesai membatalkan dalam kasus Anda). Setelah pekerja latar belakang selesai, dialog modal mengembalikan kontrol ke aplikasi Anda. Pengguna tidak dapat berinteraksi dengan UI sampai ini terjadi.

Metode lain (dengan asumsi Anda memiliki maksimum satu jendela modeless terbuka) adalah untuk mengatur ActiveForm.Enabled = false, kemudian loop pada Aplikasi, DoEvents sampai pekerja latar belakang selesai membatalkan, setelah itu Anda dapat mengatur ActiveForm.Enabled = true lagi.

Joe
sumber
5
Itu mungkin masalah, tetapi diterima secara mendasar sebagai bagian dari pertanyaan, "Bagaimana cara menunggu BackgroundWorker untuk membatalkan". Menunggu berarti Anda menunggu, Anda tidak melakukan apa pun. Ini juga termasuk pemrosesan pesan. Jika saya tidak ingin menunggu pekerja latar belakang Anda bisa menelepon .CancelAsync. Tapi itu bukan persyaratan desain di sini.
Ian Boyd
1
+1 untuk menunjukkan nilai metode CancelAsync, ayat menunggu pekerja latar belakang.
Ian Boyd
10

Hampir Anda semua bingung dengan pertanyaan itu, dan tidak mengerti bagaimana seorang pekerja digunakan.

Pertimbangkan event handler 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;
}

Dan semuanya baik-baik saja.

Sekarang tiba situasi di mana penelepon harus membatalkan penghitungan mundur karena mereka harus melakukan penghancuran roket secara otomatis.

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

    StartClaxon();
    SelfDestruct();
}

Dan ada juga situasi di mana kita perlu membuka gerbang akses ke roket, tetapi tidak saat melakukan hitungan mundur:

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

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

Dan akhirnya, kita perlu mengurangi bahan bakar roket, tapi itu tidak diperbolehkan selama hitungan mundur:

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

    if (rocketOnPad)
        OpenFuelValves();
}

Tanpa kemampuan untuk menunggu pekerja untuk membatalkan, kita harus memindahkan ketiga metode ke 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();
}

Sekarang saya bisa menulis kode seperti itu, tetapi saya tidak akan melakukannya. Saya tidak peduli, saya hanya tidak.

Ian Boyd
sumber
18
Di mana metode WaitForWorkerToFinish? ada kode sumber lengkap?
Kiquenet
4

Anda dapat memeriksa RunWorkerCompletedEventArgs di RunWorkerCompletedEventHandler untuk melihat statusnya. Berhasil, dibatalkan, atau kesalahan.

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

Pembaruan : Untuk melihat apakah pekerja Anda telah memanggil .CancelAsync () dengan menggunakan ini:

if (_worker.CancellationPending)
{
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}
Seb Nilsson
sumber
2
Syaratnya adalah CancelDoingStuff () tidak bisa kembali sampai pekerja selesai. Memeriksa bagaimana penyelesaiannya sebenarnya tidak menarik.
Ian Boyd
Maka Anda harus membuat acara. Ini benar-benar tidak ada hubungannya dengan BackgroundWorker secara khusus, Anda hanya perlu mengimplementasikan suatu acara, mendengarkan dan memecatnya ketika selesai. Dan RunWorkerCompletedEventHandler sudah selesai. Tembak acara lain.
Seb Nilsson
4

Anda tidak menunggu sampai pekerja latar belakang selesai. Itu cukup banyak mengalahkan tujuan meluncurkan utas terpisah. Sebagai gantinya, Anda harus membiarkan metode Anda selesai, dan memindahkan kode apa pun yang bergantung pada penyelesaian ke tempat lain. Anda membiarkan pekerja memberi tahu Anda ketika sudah selesai dan memanggil kode yang tersisa kemudian.

Jika Anda ingin menunggu sesuatu selesai menggunakan konstruk threading berbeda yang menyediakan WaitHandle.

Joel Coehoorn
sumber
1
Bisakah Anda menyarankan satu? Pekerja latar tampaknya menjadi satu-satunya konstruk threading yang dapat mengirim pemberitahuan ke utas yang membuat objek BackgroundWorker.
Ian Boyd
Backgroundworker adalah konstruk UI . Itu hanya menggunakan acara yang kode Anda sendiri masih perlu tahu cara menelepon. Tidak ada yang menghentikan Anda dari membuat delegasi Anda sendiri untuk itu.
Joel Coehoorn
3

Mengapa Anda tidak bisa hanya mengikat ke BackgroundWorker.RunWorkerCompleted Event. Ini adalah panggilan balik yang akan "Terjadi ketika operasi latar belakang selesai, telah dibatalkan, atau telah menimbulkan pengecualian."

Rick Minerich
sumber
1
Karena orang yang menggunakan objek DoesStuff telah memintanya untuk membatalkan. Sumber daya bersama yang digunakan objek akan diputus, dihapus, dibuang, ditutup, dan perlu diketahui bahwa itu dilakukan agar saya dapat melanjutkan.
Ian Boyd
1

Saya tidak mengerti mengapa Anda ingin menunggu BackgroundWorker selesai; sepertinya benar-benar kebalikan dari motivasi untuk kelas.

Namun, Anda bisa memulai setiap metode dengan panggilan ke pekerja. IsBusy dan minta mereka keluar jika sedang berjalan.

Austin Salonen
sumber
Pilihan kata yang salah; Saya tidak menunggu sampai selesai.
Ian Boyd
1

Hanya ingin mengatakan bahwa saya datang ke sini karena saya membutuhkan pekerja latar belakang untuk menunggu sementara saya menjalankan proses async sementara dalam satu lingkaran, perbaikan saya jauh lebih mudah daripada semua hal lain ini ^^

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

Baru saja tahu saya akan berbagi karena ini adalah tempat saya akhirnya mencari solusi. Juga, ini adalah posting pertama saya di stack overflow jadi jika itu buruk atau apa pun saya suka kritik! :)

Connor Williams
sumber
0

Hm mungkin saya tidak menjawab pertanyaan Anda dengan benar.

Backgroundworker memanggil acara WorkerCompleted sekali 'workermethod' nya (metode / fungsi / sub yang menangani backgroundworker.doWork-event ) selesai sehingga tidak perlu untuk memeriksa apakah BW masih berjalan. Jika Anda ingin menghentikan pekerja Anda, periksa pembatalan properti tertunda di dalam 'metode pekerja' Anda.

Stephan
sumber
saya mengerti bahwa pekerja harus memantau properti PembatalanPending dan keluar sesegera mungkin. Tetapi bagaimana orang di luar, yang telah meminta pekerja latar belakang membatalkan, menunggu untuk dilakukan?
Ian Boyd
Acara WorkCompleted masih akan aktif.
Joel Coehoorn
"Tapi bagaimana orang di luar, yang telah meminta pekerja latar belakang membatalkan, menunggu sampai selesai?"
Ian Boyd
Anda menunggu sampai selesai dengan menunggu acara WorkCompleted menyala. Jika Anda ingin mencegah pengguna memiliki kecocokan mengklik di seluruh GUI Anda, maka Anda dapat menggunakan salah satu solusi yang diusulkan oleh @ Jo dalam jawabannya di atas (nonaktifkan formulir, atau tampilkan modal sesuatu). Dengan kata lain, biarkan loop sistem menganggur menunggu Anda; itu akan memberi tahu Anda ketika sudah selesai (dengan menembakkan acara).
Geoff
0

Alur kerja suatu BackgroundWorkerobjek pada dasarnya mengharuskan Anda untuk menangani RunWorkerCompletedacara untuk kedua kasus eksekusi normal dan pembatalan pengguna. Inilah sebabnya mengapa properti RunWorkerCompletedEventArgs.Cancelled ada. Pada dasarnya, melakukan ini dengan benar mengharuskan Anda menganggap metode Batal Anda sebagai metode asinkron itu sendiri.

Ini sebuah contoh:

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

Jika Anda benar-benar tidak ingin metode Anda keluar, saya sarankan meletakkan bendera seperti AutoResetEventpada turunan BackgroundWorker, kemudian menimpa OnRunWorkerCompleteduntuk mengatur bendera. Ini masih agak kludgy; Saya akan merekomendasikan memperlakukan acara batal seperti metode asinkron dan melakukan apa pun yang sedang dilakukan di RunWorkerCompletedhandler.

OwenP
sumber
Memindahkan kode ke RunWorkerCompleted bukan tempatnya, dan tidak cantik.
Ian Boyd
0

Saya agak terlambat ke pesta di sini (sekitar 4 tahun) tetapi bagaimana dengan menyiapkan utas sinkron yang dapat menangani loop sibuk tanpa mengunci UI, kemudian meminta panggilan balik dari utas itu menjadi konfirmasi bahwa BackgroundWorker telah selesai membatalkan ?

Sesuatu seperti ini:

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

Intinya yang dilakukan adalah menjalankan utas lain untuk dijalankan di latar belakang yang hanya menunggu di loop sibuk untuk melihat apakah MyWorkertelah selesai. Setelah MyWorkerselesai membatalkan utas akan keluar dan kita dapat menggunakannya AsyncCallbackuntuk menjalankan metode apa pun yang kita butuhkan untuk mengikuti pembatalan yang berhasil - itu akan berfungsi seperti acara psuedo. Karena ini terpisah dari utas UI, ia tidak akan mengunci UI sementara kami menunggu untuk MyWorkermenyelesaikan pembatalan. Jika niat Anda adalah mengunci dan menunggu pembatalan maka ini tidak berguna bagi Anda, tetapi jika Anda hanya ingin menunggu sehingga Anda dapat memulai proses lain maka ini berfungsi dengan baik.

Infotekka
sumber
0

Saya tahu ini benar-benar terlambat (5 tahun) tetapi yang Anda cari adalah menggunakan Thread dan SynchronizationContext . Anda harus membuat panggilan UI kembali ke utas UI "dengan tangan" daripada membiarkan Framework melakukannya secara otomatis.

Ini memungkinkan Anda menggunakan Thread yang bisa Anda Tunggu jika perlu.

Tom Padilla
sumber
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
Nitesh
sumber
Apa pertanyaanmu? Dalam SO, Anda harus spesifik saat mengajukan pertanyaan
Alma Do
@ Ini adalah jawaban, bukan pertanyaan.
Kode L ღ ver
0

Solusi Fredrik Kalseth untuk masalah ini adalah yang terbaik yang saya temukan sejauh ini. Penggunaan solusi lain Application.DoEvent()yang dapat menyebabkan masalah atau tidak berfungsi. Biarkan saya memberikan solusinya ke kelas yang dapat digunakan kembali. Karena BackgroundWorkertidak disegel, kita dapat mengambil kelas kita darinya:

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

Dengan bendera dan penguncian yang tepat, kami memastikan bahwa _resetEvent.WaitOne()hanya dipanggil jika beberapa pekerjaan telah dimulai, jika tidak_resetEvent.Set(); mungkin tidak akan pernah dipanggil!

_resetEvent.Set();Try- akhirnya memastikan bahwa akan dipanggil, bahkan jika pengecualian harus terjadi di DoWork-handler kami. Kalau tidak, aplikasi bisa membeku selamanya saat meneleponCancelSync !

Kami akan menggunakannya seperti ini:

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

Anda juga dapat menambahkan penangan ke RunWorkerCompletedacara seperti yang ditunjukkan di sini:
     BackgroundWorker Class (dokumentasi Microsoft) .

Olivier Jacot-Descombes
sumber
0

Menutup formulir menutup logfile terbuka saya. Pekerja latar belakang saya menulis file log itu, jadi saya tidak bisa membiarkannyaMainWin_FormClosing() selesai sampai pekerja latar belakang saya berakhir. Jika saya tidak menunggu pekerja latar belakang saya untuk berhenti, pengecualian terjadi.

Mengapa ini sangat sulit?

Sebuah Thread.Sleep(1500)karya sederhana , tetapi menunda shutdown (jika terlalu lama), atau menyebabkan pengecualian (jika terlalu pendek).

Untuk mematikan tepat setelah pekerja latar belakang berakhir, cukup gunakan variabel. Ini bekerja untuk saya:

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.)
A876
sumber
0

Anda dapat menggagalkan acara RunWorkerCompleted. Bahkan jika Anda sudah menambahkan event handler untuk _worker, Anda bisa menambahkan yang lain yang akan mereka jalankan sesuai urutan penambahannya.

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

ini bisa bermanfaat jika Anda memiliki beberapa alasan mengapa pembatalan dapat terjadi, membuat logika satu penangan RunWorkerCompleted lebih rumit dari yang Anda inginkan. Misalnya, membatalkan ketika pengguna mencoba menutup formulir:

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

Saya menggunakan asyncmetode dan awaituntuk menunggu pekerja menyelesaikan tugasnya:

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

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

dan dalam DoWorkmetode:

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

Anda juga dapat merangkum whileloop dalam DoWorkdengan try ... catchuntuk mengatur _isBusyadalah falsepengecualian. Atau, cukup periksa _worker.IsBusydi StopAsyncloop while.

Berikut ini adalah contoh implementasi penuh:

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

Untuk menghentikan pekerja dan menunggu hingga akhir:

await myBackgroundWorker.StopAsync();

Masalah dengan metode ini adalah:

  1. Anda harus menggunakan metode async sepanjang jalan.
  2. menunggu Task.Delay tidak akurat. Di PC saya, Task.Delay (1) sebenarnya menunggu ~ 20 ms.
Anthony
sumber
-2

oh man, beberapa di antaranya menjadi sangat rumit. yang perlu Anda lakukan adalah memeriksa properti BackgroundWorker.CancellationPending di dalam handler DoWork. Anda dapat memeriksanya kapan saja. setelah tertunda, atur e.Cancel = True dan bail dari metode.

// metode di sini private void Worker_DoWork (pengirim objek, DoWorkEventArgs e) {BackgroundWorker bw = (pengirim sebagai BackgroundWorker);

// do stuff

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

// do other stuff

}


sumber
1
Dan dengan solusi ini, bagaimana orang yang menelepon CancelAsync harus menunggu pekerja latar belakang untuk membatalkan?
Ian Boyd