कैसे एक 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.
    }
}

बैकग्राउंडवॉकर के लिए कोई कैसे इंतजार कर सकता है?


अतीत में लोगों ने कोशिश की है:

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

यह वह नहीं है, यह वह नहीं है जो मैं कर रहा हूं, और यह वह नहीं है जो यहां पूछा जा रहा है। अगर ऐसा होता तो बैकग्राउंड वर्कर का इस्तेमाल करने का कोई मतलब नहीं होता।

जवाबों:


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
यह यूआई को ब्लॉक करेगा (उदाहरण के लिए कोई प्रतिनिधि नहीं) यदि पृष्ठभूमि कार्यकर्ता को रद्द करने में लंबा समय लगता है। यूआई के साथ बातचीत रोकने के लिए निम्नलिखित में से किसी एक का उपयोग करना बेहतर है: stackoverflow.com/questions/123661/…
जो

1
+1 केवल डॉक्टर ने जो आदेश दिया ... हालांकि मैं @ जॉय से सहमत हूं यदि रद्द अनुरोध एक सेकंड से अधिक समय ले सकता है।
डॉटजेओ

क्या तब होता है जब WaitAsne से पहले CancelAsync हैंडल हो जाता है? या क्या रद्द बटन केवल एक बार काम करता है।
कोडिंगबारफील्ड

6
मुझे ((BackgroundWorker)sender).CancellationPendingरद्द करने की घटना की जाँच करने की आवश्यकता थी
लुक

1
जैसा कि ल्यूक ने उल्लेख किया है कि यह रद्द की गई संपत्ति नहीं है, जिसे रद्द किया जाना चाहिए, लेकिन रद्द किया जाना चाहिए। दूसरे, जोएल कोएहॉर्न के रूप में उल्लेख किया गया है, थ्रेड को समाप्त करने के लिए इंतजार करने से पहली जगह में एक थ्रेड का उपयोग करने के उद्देश्य को हराया।
कप्तान संवेदनशील

15

वहाँ के साथ एक समस्या है इस प्रतिक्रिया के । यूआई को प्रतीक्षा करते समय संदेशों को संसाधित करना जारी रखना होगा, अन्यथा यह पुन: नहीं होगा, जो कि एक समस्या है यदि आपका पृष्ठभूमि कार्यकर्ता रद्द अनुरोध का जवाब देने में लंबा समय लेता है।

एक दूसरा दोष यह है कि _resetEvent.Set() कभी भी कॉल नहीं किया जाएगा यदि कार्यकर्ता थ्रेड एक अपवाद फेंकता है - मुख्य धागे को अनिश्चित काल तक प्रतीक्षा करते हुए - हालांकि यह दोष आसानी से एक कोशिश / अंत में ब्लॉक के साथ तय किया जा सकता है।

इसका एक तरीका यह है कि एक मोडल डायलॉग प्रदर्शित किया जाए जिसमें टाइमर हो जो बार-बार चेक करता हो कि बैकग्राउंड वर्कर ने काम खत्म कर दिया है (या आपके मामले में रद्द कर दिया गया है)। एक बार पृष्ठभूमि कार्यकर्ता समाप्त हो जाने के बाद, मोडल संवाद आपके एप्लिकेशन पर नियंत्रण लौटाता है। ऐसा होने तक उपयोगकर्ता UI के साथ सहभागिता नहीं कर सकता है।

एक अन्य विधि (मान लें कि आपके पास अधिकतम एक मॉडल विंडो खुली हुई है) ActiveForm.Enabled = false, फिर Application, DoEvents पर सेट करना है, जब तक कि बैकग्राउंड वर्कर ने रद्द करना समाप्त नहीं कर दिया है, जिसके बाद आप ActiveForm.Enabled = true को फिर से सेट कर सकते हैं।


5
यह एक समस्या हो सकती है, लेकिन इसे मौलिक रूप से इस सवाल के हिस्से के रूप में स्वीकार किया जाता है, "कैसे रद्द करने के लिए एक बैकग्राउंडर की प्रतीक्षा करें "। प्रतीक्षा का अर्थ है कि आप प्रतीक्षा करें, आप कुछ और न करें। इसमें प्रोसेसिंग संदेश भी शामिल हैं। अगर मैं पृष्ठभूमि कार्यकर्ता के लिए इंतजार नहीं करना चाहता था तो आप कॉल कर सकते हैं। CancelAsync लेकिन यह यहाँ डिजाइन की आवश्यकता नहीं है।
इयान बॉयड

1
+1 रद्द करने का तरीका रद्द करने के लिए CancelAsync विधि, पृष्ठभूमि कार्यकर्ता के लिए छंद इंतजार कर रहा है।
इयान बॉयड

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 विधि कहाँ है? कोई भी पूर्ण स्रोत कोड?
कीकनेट

4

आप RunWorkerCompletedEventArgs में RunWorkerCompletedEventHandler में देख सकते हैं कि स्थिति क्या थी। सफलता, रद्द या कोई त्रुटि।

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

अद्यतन : यह देखने के लिए कि क्या आपके कार्यकर्ता ने कॉल किया है। इसे इस्तेमाल करके।

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

2
आवश्यकता यह है कि CancelDoingStuff () कार्यकर्ता के पूरा होने तक वापस नहीं आ सकता है। यह देखना कि यह वास्तव में कैसे पूरा हुआ, ब्याज की नहीं है।
इयान बॉयड

फिर आपको एक ईवेंट बनाना होगा। यह विशेष रूप से BackgroundWorker के साथ करने के लिए कुछ भी नहीं मिला है, आपको बस एक घटना को लागू करने की जरूरत है, इसे सुनें और जब किया जाए तो आग लग जाए। और RunWorkerCompletedEventHandler जब यह किया जाता है। एक और घटना आग।
एसबी निल्सन

4

आप पृष्ठभूमि कार्यकर्ता के पूरा होने की प्रतीक्षा नहीं करते हैं । यह बहुत अलग थ्रेड लॉन्च करने के उद्देश्य को हरा देता है। इसके बजाय, आपको अपनी विधि को पूरा करने देना चाहिए, और किसी भी कोड को स्थानांतरित करना चाहिए जो किसी भिन्न स्थान पर पूरा होने पर निर्भर करता है। आप कार्यकर्ता को यह बताएंगे कि यह कब किया गया है और फिर किसी शेष कोड को कॉल करें।

यदि आप किसी अन्य थ्रेडिंग निर्माण का उपयोग करने के लिए कुछ प्रतीक्षा करना चाहते हैं जो एक WaitHandle प्रदान करता है।


1
क्या आप एक सुझाव दे सकते हैं? एक पृष्ठभूमि कार्यकर्ता केवल थ्रेडिंग निर्माण प्रतीत होता है जो उस बैकग्राउंड को सूचना भेज सकता है जिसने बैकग्राउंडर ऑब्जेक्ट का निर्माण किया था।
इयान बॉयड

बैकग्राउंडर एक यूआई निर्माण है। यह सिर्फ एक घटना का उपयोग करता है जिसे आपके अपने कोड को अभी भी जानना है कि कैसे कॉल करना है। उसके लिए अपने स्वयं के प्रतिनिधि बनाने से कुछ भी नहीं रोक रहा है।
जोएल कोएहॉर्न

3

आप सिर्फ BackgroundWorker.RunWorkerCompleted Event में क्यों नहीं टिक सकते। यह एक कॉलबैक है जो "तब होगा जब पृष्ठभूमि ऑपरेशन पूरा हो चुका है, रद्द कर दिया गया है, या एक अपवाद उठाया है।"


1
क्योंकि जो व्यक्ति DoStuff ऑब्जेक्ट का उपयोग कर रहा है, उसने इसे रद्द करने के लिए कहा है। साझा किए गए संसाधन जो ऑब्जेक्ट का उपयोग करते हैं, वे डिस्कनेक्ट करने, हटाने, निपटाने, बंद करने के बारे में हैं, और यह जानना आवश्यक है कि ऐसा किया गया है इसलिए मैं आगे बढ़ सकता हूं।
इयान बॉयड

1

मुझे समझ में नहीं आता है कि आप एक BackgroundWorker के पूरा होने की प्रतीक्षा क्यों करना चाहते हैं; यह वास्तव में कक्षा के लिए प्रेरणा के बिल्कुल विपरीत जैसा लगता है।

हालाँकि, आप कार्यकर्ता को कॉल के साथ हर विधि शुरू कर सकते हैं। लेकिन अगर यह चल रहा है तो उन्हें बाहर निकलें।


शब्दों का बुरा विकल्प; मैं इसके पूरा होने का इंतजार नहीं कर रहा हूं।
इयान बॉयड

1

बस कहना चाहता हूं कि मैं यहां आया था क्योंकि मुझे इंतजार करने के लिए एक पृष्ठभूमि कार्यकर्ता की आवश्यकता है, जबकि मैं एक लूप में एक async प्रक्रिया चला रहा था, मेरा फिक्स इस अन्य सामान की तुलना में आसान था ^ ^

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

मुझे लगा कि मैं साझा करूंगा क्योंकि यह वह जगह है जहां मैं समाधान के लिए खोज करते हुए समाप्त हुआ। इसके अलावा, यह स्टैक ओवरफ्लो पर मेरी पहली पोस्ट है, अगर इसके बुरे या कुछ भी मैं आलोचकों से प्यार करता हूँ! :)


0

हम्म शायद मुझे आपका सवाल सही नहीं लग रहा है।

बैकग्राउंडर एक बार अपने 'वर्कमैथोड' (मेथडवर्क / फंक्शन / सब - बैकड्रॉप को संभालता है जो वर्करकंप्लीटेड इवेंट को कॉल करता है। बैकवॉटर अभी भी चल रहा है तो चेकिंग की कोई आवश्यकता नहीं है)। यदि आप अपने कार्यकर्ता को अपने 'कार्यकर्ता पद्धति' के अंदर लंबित संपत्ति को रद्द करने से रोकना चाहते हैं


मैं समझता हूं कि कार्यकर्ता को कैंसलेशनेशन प्रॉपर्टी की निगरानी करना होगा और जितनी जल्दी हो सके बाहर निकल जाना चाहिए। लेकिन बाहर के व्यक्ति को, जिसने पृष्ठभूमि के कार्यकर्ता को रद्द करने का अनुरोध किया है, उसके लिए इंतजार किया जाना चाहिए?
इयान बॉयड

अभी भी कार्यस्थल पर आग लग जाएगी।
जोएल कोएहॉर्न

"लेकिन बाहर के व्यक्ति, जिसने पृष्ठभूमि के कार्यकर्ता को रद्द करने का अनुरोध किया है, उसके लिए इंतजार कैसे किया जाए?"
इयान बोयड

आप इसके लिए प्रतीक्षा करें कि आप कार्यस्थल की आग के लिए प्रतीक्षा कर रहे हैं। यदि आप उपयोगकर्ता को अपने GUI पर एक क्लिक करने से रोकना चाहते हैं तो आप @Joe द्वारा प्रस्तावित समाधानों में से एक का उपयोग ऊपर दिए गए उत्तर में कर सकते हैं (फ़ॉर्म को अक्षम करें, या कुछ मोडल दिखाएं)। दूसरे शब्दों में, सिस्टम के बेकार लूप को आपके लिए इंतजार करने दें; यह आपको बताएगा कि यह कब समाप्त हो गया है (घटना को फायर करके)।
ज्योफ

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 में मूविंग कोड वह नहीं है जहां वह है, और सुंदर नहीं है।
इयान बॉयड

0

मैं यहां पार्टी में थोड़ी देर से (लगभग 4 साल) हूं, लेकिन एक अतुल्यकालिक धागे की स्थापना के बारे में क्या है जो यूआई को लॉक किए बिना एक व्यस्त लूप को संभाल सकता है, फिर उस थ्रेड से कॉलबैक की पुष्टि हो सकती है कि बैकग्राउंडवर्क रद्द कर दिया गया है ?

कुछ इस तरह:

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कि सफल रद्दीकरण का पालन करने के लिए हमें जो भी विधि की आवश्यकता है - वह एक प्यूसीडो-ईवेंट की तरह काम करेगा। चूंकि यह UI थ्रेड से अलग है, इसलिए हम UI को लॉक नहीं करेंगे जबकि हम MyWorkerरद्द करने का इंतजार करते हैं । यदि आपका इरादा वास्तव में लॉक करना है और रद्द करने की प्रतीक्षा करना है तो यह आपके लिए बेकार है, लेकिन यदि आप बस इंतजार करना चाहते हैं तो आप एक और प्रक्रिया शुरू कर सकते हैं तो यह अच्छी तरह से काम करता है।


0

मुझे पता है कि यह वास्तव में देर से (5 वर्ष) है, लेकिन आप जिस चीज की तलाश कर रहे हैं वह एक थ्रेड और एक सिंक्रोनाइज़ेशनकोटेक्स्ट का उपयोग करना है । आप यूआई थ्रेड को यूआई थ्रेड पर वापस "हाथ से" करने के बजाय "ऑटो-जादुई रूप से फ्रेमवर्क करने देते हैं" करने जा रहे हैं।

यह आपको एक थ्रेड का उपयोग करने की अनुमति देता है जिसे आप ज़रूरत पड़ने पर प्रतीक्षा कर सकते हैं।


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

आपका सवाल क्या हैं? SO में आपको प्रश्न पूछते समय विशिष्ट होना चाहिए
अल्मा दो

@ यह एक सवाल का जवाब नहीं है।
कोड एल Code वेर

0

फ्रेड्रिक कालसेथ ने इस समस्या का हल सबसे अच्छा मैं अब तक पाया है। अन्य समाधान उपयोग 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();बुलाया जाएगा, भले ही हमारे डॉकवर्क-हैंडलर में कोई अपवाद हो। अन्यथा फोन करते समय एप्लिकेशन हमेशा के लिए फ्रीज हो सकता है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 Class (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

यहाँ पूर्ण कार्यान्वयन का एक उदाहरण है:

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. टास्क का इंतजार करें। मेरे पीसी पर टास्क.डेले (1) वास्तव में इंतजार कर रहा है ~ 20ms।

-2

ओह यार, इनमें से कुछ ने हास्यास्पद रूप से जटिल हो गया है। आपको केवल DoWork हैंडलर के अंदर BackgroundWorker.CancellationPending संपत्ति की जांच करनी होगी। आप इसे कभी भी देख सकते हैं। एक बार यह लंबित होने के बाद, e.Cancel = True और पद्धति से जमानत दें।

// विधि यहाँ निजी शून्य Work__oWork (ऑब्जेक्ट प्रेषक, DoWorkEventArgs e) {BackgroundWorker bw = (बैकग्राउंडवेयर के रूप में प्रेषक);

// do stuff

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

// do other stuff

}


1
और इस समाधान के साथ रद्द करने वाले व्यक्ति को रद्द करने के लिए पृष्ठभूमि कार्यकर्ता के लिए इंतजार करने के लिए मजबूर करने वाला व्यक्ति कैसे है?
इयान बॉयड 15
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.