سؤال

اعتبر أ افتراضية طريقة الكائن الذي يفعل الأشياء لك:

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

    ...

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

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

كيف يمكن للمرء أن ينتظر حتى يتم إنجاز BackworkWorker؟


لقد حاول الناس في الماضي:

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

لكن هذا الجمود, ، لأن IsBusy لا يتم مسحها إلا بعد RunWorkerCompleted تتم معالجة الحدث، ولا يمكن معالجة هذا الحدث حتى يصبح التطبيق خاملاً.لن يصبح التطبيق خاملاً حتى ينتهي العامل.(بالإضافة إلى أنها حلقة مزدحمة - مثيرة للاشمئزاز.)

أضاف آخرون مقترحًا kludging إلى:

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 worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);

إنه لا هذا هو لا ما أفعله، وهذا هو لا ما هو المطلوب هنا.إذا كان الأمر كذلك، فلن يكون هناك أي فائدة في استخدام عامل الخلفية.

هل كانت مفيدة؟

المحلول

إذا فهمت متطلباتك بشكل صحيح، فيمكنك القيام بشيء مثل هذا (لم يتم اختبار الكود، ولكنه يوضح الفكرة العامة):

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
}

نصائح أخرى

هناك مشكلة مع هذا إجابة.تحتاج واجهة المستخدم إلى الاستمرار في معالجة الرسائل أثناء انتظارك، وإلا فلن يتم إعادة الرسم، الأمر الذي سيكون مشكلة إذا استغرق عامل الخلفية لديك وقتًا طويلاً للرد على طلب الإلغاء.

العيب الثاني هو ذلك _resetEvent.Set() لن يتم استدعاؤه أبدًا إذا ألقى الخيط العامل استثناءً - تاركًا الخيط الرئيسي ينتظر إلى أجل غير مسمى - ولكن يمكن إصلاح هذا الخلل بسهولة باستخدام كتلة المحاولة/الأخيرة.

إحدى الطرق للقيام بذلك هي عرض مربع حوار مشروط يحتوي على مؤقت يتحقق بشكل متكرر مما إذا كان عامل الخلفية قد انتهى من العمل (أو انتهى من الإلغاء في حالتك).بمجرد انتهاء عامل الخلفية، يعيد مربع الحوار المشروط التحكم إلى تطبيقك.لا يمكن للمستخدم التفاعل مع واجهة المستخدم حتى يحدث ذلك.

هناك طريقة أخرى (بافتراض أن لديك نافذة واحدة غير مشروطة كحد أقصى مفتوحة) وهي تعيين ActiveForm.Enabled = false، ثم التكرار على Application,DoEvents حتى ينتهي عامل الخلفية من الإلغاء، وبعد ذلك يمكنك تعيين ActiveForm.Enabled = true مرة أخرى.

جميعكم تقريبًا في حيرة من هذا السؤال، ولا تفهمون كيفية استخدام العامل.

خذ بعين الاعتبار معالج الأحداث 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();
}

الآن يمكنني كتابة الكود الخاص بي بهذه الطريقة، لكنني لن أفعل ذلك.لا أهتم، أنا لست كذلك.

يمكنك التحقق من 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");
}

أنت لا انتظر حتى يكتمل عامل الخلفية.وهذا يتعارض إلى حد كبير مع الغرض من إطلاق موضوع منفصل.بدلاً من ذلك، يجب عليك ترك طريقتك تنتهي، ونقل أي تعليمات برمجية تعتمد على الإكمال إلى مكان مختلف.يمكنك السماح للعامل بإخبارك عند الانتهاء من ذلك والاتصال بأي رمز متبقي بعد ذلك.

أذا أردت انتظر لإكمال شيء ما، استخدم بنية ترابط مختلفة توفر WaitHandle.

لماذا لا يمكنك ربط الحدث بـBackgroundWorker.RunWorkerCompleted.إنه رد اتصال سيحدث "عند اكتمال عملية الخلفية، أو إلغائها، أو ظهور استثناء."

لا أفهم سبب رغبتك في الانتظار حتى يكتمل عامل الخلفية؛يبدو حقًا أنه عكس الدافع للفصل تمامًا.

ومع ذلك، يمكنك بدء كل طريقة باستدعاء العامل.IsBusy وإخراجه إذا كان قيد التشغيل.

أريد فقط أن أقول إنني أتيت إلى هنا لأنني بحاجة إلى عامل في الخلفية للانتظار أثناء تشغيل عملية غير متزامنة أثناء وجودي في حلقة، كان الإصلاح الخاص بي أسهل بكثير من كل هذه الأشياء الأخرى ^^

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

لقد فكرت للتو في المشاركة لأن هذا هو المكان الذي انتهى بي الأمر فيه أثناء البحث عن حل.أيضًا، هذه هي مشاركتي الأولى حول تجاوز سعة المكدس، لذا إذا كان الأمر سيئًا أو أي شيء فأنا أحب النقاد!:)

حسنًا، ربما لا أفهم سؤالك بشكل صحيح.

يستدعي عامل الخلفية الحدث WorkerCompleted بمجرد "طريقة العمل" الخاصة به (الطريقة/الوظيفة/الفرعية التي تتعامل مع الخلفيةworker.doWork-event) تم الانتهاء لذلك ليست هناك حاجة للتحقق مما إذا كان BW لا يزال قيد التشغيل.إذا كنت تريد إيقاف العامل الخاص بك، فتحقق من إلغاء الممتلكات المعلقة داخل "طريقة العامل" الخاصة بك.

سير العمل أ 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 معالج.

لقد تأخرت قليلًا عن الحفلة هنا (حوالي 4 سنوات) ولكن ماذا عن إعداد سلسلة رسائل غير متزامنة يمكنها التعامل مع حلقة مشغولة دون قفل واجهة المستخدم، ثم يكون رد الاتصال من سلسلة المحادثات هذه بمثابة تأكيد على أن 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 لتنفيذ أي طريقة نحتاجها لمتابعة الإلغاء الناجح - سيعمل مثل حدث زائف.نظرًا لأن هذا منفصل عن مؤشر ترابط واجهة المستخدم، فلن يقوم بقفل واجهة المستخدم أثناء انتظارنا MyWorker لإنهاء الإلغاء.إذا كانت نيتك حقًا هي القفل وانتظار الإلغاء، فهذا غير مفيد بالنسبة لك، ولكن إذا كنت تريد فقط الانتظار حتى تتمكن من بدء عملية أخرى، فهذا يعمل بشكل جيد.

أعلم أن هذا متأخر جدًا (5 سنوات) ولكن ما تبحث عنه هو استخدام Thread وa سياق التزامن.سيتعين عليك تنظيم مكالمات واجهة المستخدم مرة أخرى إلى مؤشر ترابط واجهة المستخدم "يدويًا" بدلاً من السماح لـ Framework بالقيام بذلك بطريقة سحرية تلقائيًا.

يتيح لك هذا استخدام مؤشر ترابط يمكنك انتظاره إذا لزم الأمر.

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

إن حل فريدريك كالسيث لهذه المشكلة هو أفضل ما وجدته حتى الآن.استخدام حلول أخرى 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 الحدث كما هو موضح هنا:
     فئة العمال الخلفية (وثائق مايكروسوفت).

يؤدي إغلاق النموذج إلى إغلاق ملف السجل المفتوح الخاص بي.يقوم عامل الخلفية الخاص بي بكتابة ملف السجل هذا، لذا لا يمكنني السماح بذلك 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.)

يمكنك التراجع عن حدث 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;
    }
}

أنا أستعمل 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. يجب عليك استخدام أساليب غير متزامنة على طول الطريق.
  2. انتظار المهمة. التأخير غير دقيق.على جهاز الكمبيوتر الخاص بي، ينتظر Task.Delay(1) فعليًا حوالي 20 مللي ثانية.

يا رجل، بعض هذه الأمور أصبحت معقدة بشكل يبعث على السخرية.كل ما عليك فعله هو التحقق من الخاصيةBackgroundWorker.CancellationPending داخل معالج DoWork.يمكنك التحقق من ذلك في أي وقت.بمجرد أن يكون الأمر معلقًا، قم بتعيين e.Cancel = True وتخلص من الطريقة.

// الطريقة هنا private void worker_dowork (مرسل الكائن ، doworkeventargs e) {backgroundworker bw = (المرسل كـ proentederworker) ؛

// do stuff

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

// do other stuff

}

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top