จะรอให้ 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()ทำให้ข้อความที่อยู่ในคิวถูกประมวลผลซึ่งทำให้เกิดปัญหา re-entrancy (.NET ไม่ใช่ re-entrant)

ฉันหวังว่าจะใช้วิธีแก้ปัญหาบางอย่างที่เกี่ยวข้องกับอ็อบเจ็กต์การซิงโครไนซ์เหตุการณ์โดยที่โค้ดรอเหตุการณ์ที่ผู้จัดการ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 ตามที่แพทย์สั่ง ... แม้ว่าฉันจะเห็นด้วยกับ @Joe หากคำขอยกเลิกอาจใช้เวลามากกว่าหนึ่งวินาที
dotjoe

จะเกิดอะไรขึ้นเมื่อมีการจัดการ CancelAsync ก่อน WaitOne หรือว่าปุ่มยกเลิกใช้งานได้เพียงครั้งเดียว
CodingBarfield

6
ฉันต้องการตรวจสอบ((BackgroundWorker)sender).CancellationPendingเพื่อรับกิจกรรมยกเลิก
Luuk

1
ดังที่ Luuk กล่าวไว้ไม่ใช่คุณสมบัติการยกเลิกที่ต้องตรวจสอบ แต่เป็นการยกเลิกกำลังดำเนินการ ประการที่สองดังที่ Joel Coehoorn กล่าวถึงการรอให้เธรดยุติการพ่ายแพ้จุดประสงค์ของการใช้เธรดในตอนแรก
Captain Sensible

15

มีปัญหากับการตอบกลับนี้ UI จำเป็นต้องประมวลผลข้อความต่อไปในขณะที่คุณกำลังรอมิฉะนั้นจะไม่ทาสีใหม่ซึ่งจะเป็นปัญหาหากผู้ทำงานเบื้องหลังของคุณใช้เวลานานในการตอบกลับคำขอยกเลิก

ข้อบกพร่องประการที่สองคือ_resetEvent.Set()จะไม่ถูกเรียกหากเธรดของผู้ปฏิบัติงานโยนข้อยกเว้น - ปล่อยให้เธรดหลักรอไปเรื่อย ๆ - อย่างไรก็ตามข้อบกพร่องนี้สามารถแก้ไขได้อย่างง่ายดายด้วยการลอง / บล็อกในที่สุด

วิธีหนึ่งในการทำเช่นนี้คือการแสดงกล่องโต้ตอบโมดอลซึ่งมีตัวจับเวลาที่ตรวจสอบซ้ำ ๆ ว่าผู้ปฏิบัติงานในพื้นหลังทำงานเสร็จหรือไม่ (หรือยกเลิกในกรณีของคุณเสร็จสิ้นแล้ว) เมื่อผู้ปฏิบัติงานทำงานเบื้องหลังเสร็จสิ้นกล่องโต้ตอบโมดอลจะส่งคืนการควบคุมไปยังแอปพลิเคชันของคุณ ผู้ใช้ไม่สามารถโต้ตอบกับ UI ได้จนกว่าสิ่งนี้จะเกิดขึ้น

อีกวิธีหนึ่ง (สมมติว่าคุณมีหน้าต่างที่ไม่มีโหมดเปิดอยู่สูงสุดหนึ่งหน้าต่าง) คือการตั้งค่า ActiveForm.Enabled = false จากนั้นวนซ้ำบน Application, DoEvents จนกว่าผู้ทำงานเบื้องหลังจะยกเลิกเสร็จสิ้นหลังจากนั้นคุณสามารถตั้งค่า ActiveForm.Enabled = true ได้อีกครั้ง


5
นั่นอาจเป็นปัญหา แต่เป็นที่ยอมรับโดยพื้นฐานแล้วว่าเป็นส่วนหนึ่งของคำถาม "วิธีรอให้ BackgroundWorker ยกเลิก" การรอหมายถึงคุณรอคุณไม่ได้ทำอะไรเลย นอกจากนี้ยังรวมถึงการประมวลผลข้อความ ถ้าฉันไม่ต้องการรอคนทำงานเบื้องหลังคุณสามารถโทรหา. CancelAsync แต่นั่นไม่ใช่ข้อกำหนดในการออกแบบที่นี่
Ian Boyd

1
+1 เพื่อชี้ให้เห็นคุณค่าของเมธอด CancelAsync โองการที่รอผู้ทำงานเบื้องหลัง
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

คุณสามารถตรวจสอบในRunWorkerCompletedEventArgsในRunWorkerCompletedEventHandlerเพื่อดูว่าสถานะคืออะไร สำเร็จยกเลิกหรือเกิดข้อผิดพลาด

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

backgroundworker คือการสร้างUI เพียงแค่ใช้เหตุการณ์ที่รหัสของคุณเองยังคงต้องรู้วิธีการโทร ไม่มีอะไรหยุดคุณจากการสร้างตัวแทนของคุณเองสำหรับสิ่งนั้น
Joel Coehoorn

3

ทำไมคุณไม่สามารถเชื่อมโยงกับเหตุการณ์ BackgroundWorker.RunWorkerCompleted เป็นการโทรกลับที่จะ "เกิดขึ้นเมื่อการดำเนินการเบื้องหลังเสร็จสิ้นถูกยกเลิกหรือมีข้อยกเว้น"


1
เนื่องจากผู้ที่ใช้อ็อบเจกต์ DoesStuff ได้ขอให้ยกเลิก ทรัพยากรที่ใช้ร่วมกันที่ออบเจ็กต์ใช้กำลังจะถูกตัดการเชื่อมต่อลบทิ้งทิ้งปิดและจำเป็นต้องรู้ว่าเสร็จแล้วเพื่อที่ฉันจะได้ดำเนินการต่อ
Ian Boyd

1

ฉันไม่เข้าใจว่าทำไมคุณถึงต้องรอให้ BackgroundWorker ดำเนินการให้เสร็จสิ้น ดูเหมือนว่าจะตรงข้ามกับแรงจูงใจในชั้นเรียนจริงๆ

อย่างไรก็ตามคุณสามารถเริ่มทุกวิธีด้วยการโทรหาผู้ปฏิบัติงาน IsBusy และให้พวกเขาออกหากกำลังทำงานอยู่


การเลือกคำที่ไม่ถูกต้อง ฉันไม่ได้รอให้มันเสร็จสมบูรณ์
Ian Boyd

1

แค่อยากบอกว่าฉันมาที่นี่เพราะฉันต้องการคนทำงานเบื้องหลังเพื่อรอในขณะที่ฉันกำลังเรียกใช้กระบวนการ async ในขณะที่วนซ้ำการแก้ไขของฉันทำได้ง่ายกว่าสิ่งอื่น ๆ ทั้งหมดนี้ ^^

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

แค่คิดว่าฉันจะแบ่งปันเพราะนี่คือจุดที่ฉันลงเอยด้วยการค้นหาวิธีแก้ปัญหา นอกจากนี้นี่เป็นโพสต์แรกของฉันใน stack overflow ดังนั้นถ้ามันไม่ดีหรืออะไรก็ตามฉันชอบนักวิจารณ์! :)


0

หืมบางทีฉันอาจจะตอบคำถามคุณไม่ถูก

backgroundworker เรียกเหตุการณ์ WorkerCompleted เมื่อ 'workermethod' (method / function / sub ที่จัดการ backgroundworker.doWork-event ) เสร็จสิ้นดังนั้นจึงไม่จำเป็นต้องตรวจสอบว่า BW ยังคงทำงานอยู่หรือไม่ หากคุณต้องการหยุดคนงานของคุณให้ตรวจสอบคุณสมบัติที่รอการยกเลิกใน "วิธีการของผู้ปฏิบัติงาน"


ฉันเข้าใจว่าผู้ปฏิบัติงานต้องตรวจสอบทรัพย์สินที่กำลังดำเนินการยกเลิกและออกโดยเร็วที่สุด แต่ยังไงคนข้างนอกที่ขอคนทำงานเบื้องหลังก็ยกเลิกรอให้เรียบร้อย
Ian Boyd

เหตุการณ์ WorkCompleted จะยังคงเริ่มทำงาน
Joel Coehoorn

“ แต่คนที่อยู่ข้างนอกที่ขอคนทำงานเบื้องหลังจะยกเลิกยังไงรอให้เสร็จ”
Ian Boyd

คุณรอให้เสร็จสิ้นโดยรอให้เหตุการณ์ WorkCompleted เริ่มทำงาน หากคุณต้องการป้องกันไม่ให้ผู้ใช้คลิกพอดีกับ GUI ของคุณคุณสามารถใช้หนึ่งในวิธีแก้ปัญหาที่ @Joe เสนอในคำตอบด้านบน (ปิดใช้งานแบบฟอร์มหรือแสดงคำกริยา) กล่าวอีกนัยหนึ่งคือปล่อยให้ลูปที่ไม่ได้ใช้งานของระบบรอคุณอยู่ มันจะแจ้งให้คุณทราบเมื่อเสร็จสิ้น (โดยการเริ่มต้นเหตุการณ์)
Geoff

0

ขั้นตอนการทำงานของBackgroundWorkerออบเจ็กต์โดยพื้นฐานแล้วคุณต้องจัดการกับRunWorkerCompletedเหตุการณ์สำหรับทั้งการดำเนินการตามปกติและกรณีการใช้งานการยกเลิกผู้ใช้ นี่คือสาเหตุที่คุณสมบัติRunWorkerCompletedEventArgs.Cancelledมีอยู่ โดยพื้นฐานแล้วการดำเนินการนี้อย่างถูกต้องคุณต้องพิจารณาว่าวิธีการยกเลิกของคุณเป็นวิธีอะซิงโครนัสในตัวเอง

นี่คือตัวอย่าง:

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จากนั้นจึงแทนที่OnRunWorkerCompletedเพื่อตั้งค่าสถานะ มันยังคงเป็นสิ่งที่น่ารังเกียจอยู่ ฉันขอแนะนำให้ปฏิบัติต่อเหตุการณ์การยกเลิกเช่นวิธีอะซิงโครนัสและทำสิ่งที่กำลังทำอยู่ใน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เพื่อดำเนินการวิธีใดก็ได้ที่เราต้องการเพื่อติดตามการยกเลิกที่สำเร็จ - มันจะทำงานเหมือน psuedo-event เนื่องจากสิ่งนี้แยกจากเธรด UI จึงไม่ล็อก UI ในขณะที่เรารอMyWorkerให้ยกเลิกเสร็จสิ้น หากความตั้งใจของคุณคือการล็อกและรอการยกเลิกสิ่งนี้ก็ไร้ประโยชน์สำหรับคุณ แต่ถ้าคุณเพียงแค่ต้องการรอเพื่อให้คุณสามารถเริ่มกระบวนการใหม่ได้ก็จะได้ผลดี


0

ฉันรู้ว่านี้เป็นจริงปลายปี (5 ปี) แต่สิ่งที่คุณกำลังมองหาคือการใช้กระทู้และSynchronizationContext คุณจะต้องเรียก UI ของจอมพลกลับไปที่เธรด UI "ด้วยมือ" แทนที่จะปล่อยให้ Framework ทำแบบอัตโนมัติอย่างน่าอัศจรรย์

สิ่งนี้ช่วยให้คุณสามารถใช้เธรดที่คุณสามารถรอได้หากจำเป็น


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

คำถามของคุณคืออะไร? ดังนั้นคุณต้องเจาะจงเมื่อถามคำถาม
แอลมาโด

@ นี่คือคำตอบไม่ใช่คำถาม
รหัส L ღเวอร์ชั่น

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();อาจไม่ถูกเรียก!

ในที่สุดการทดลองจะช่วยให้มั่นใจได้ว่า_resetEvent.Set();จะถูกเรียกแม้ว่าจะมีข้อยกเว้นเกิดขึ้นในตัวจัดการ DoWork ของเราก็ตาม มิฉะนั้นแอปพลิเคชันอาจหยุดทำงานตลอดไปเมื่อโทร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 ชั้น (เอกสาร Microsoft)


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วงในDoWorkกับtry ... catchชุด_isBusyคือfalseในข้อยกเว้น หรือเพียงแค่ตรวจสอบ_worker.IsBusyในStopAsyncลูป while

นี่คือตัวอย่างของการใช้งานแบบเต็ม:

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. คุณต้องใช้วิธี async ตลอดทาง
  2. รอภารกิจความล่าช้าไม่ถูกต้อง บนพีซีของฉัน Task.Delay (1) รออยู่ ~ 20ms จริงๆ

-2

โอ้มนุษย์บางส่วนมีความซับซ้อนอย่างน่าขัน สิ่งที่คุณต้องทำคือตรวจสอบคุณสมบัติ BackgroundWorker.CancellationPending ภายในตัวจัดการ DoWork คุณสามารถตรวจสอบได้ตลอดเวลา เมื่อรอดำเนินการตั้งค่า e.Cancel = True และประกันตัวจากวิธีการ

// วิธีการที่นี่โมฆะส่วนตัว 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.