4

私は一般的にC#とオブジェクト指向プログラミングに不慣れです。ユーザーがプロセスの途中で停止できるように、GUIに「キャンセル」ボタンを実装しようとしています。

私はこの質問を読みました:停止/キャンセルボタンを実装する方法は?そして、backgroundWorkerは私にとって良いオプションであると判断しましたが、与えられた例では、backgroundWorkerに引数を渡す方法を説明していません。

私の問題は、プロセスを停止するように引数をbackgroundWorkerに渡す方法がわからないことです。私はbackgroundWorkerを停止させることしかできませんでした。

これを学習するために、次のコードを作成しました。フォームには2つのボタン(buttonStartとbuttonStop)とbackgroundWorker(backgroundWorkerStopCheck)があります。

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
using System.Timers;

namespace TestBackgroundWorker
{
    public partial class Form1 : Form
    {
        public Form1()
        {         
            InitializeComponent();

            // Set the background worker to allow the user to stop the process. 
            backgroundWorkerStopCheck.WorkerSupportsCancellation = true;
        }

        private System.Timers.Timer myTimer;

        private void backgroundWorkerStopCheck_DoWork(object sender, DoWorkEventArgs e)
        {
            //If cancellation is pending, cancel work.  
            if (backgroundWorkerStopCheck.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            // Notify the backgroundWorker that the process is starting.
            backgroundWorkerStopCheck.RunWorkerAsync();
            LaunchCode();
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            // Tell the backgroundWorker to stop process.
            backgroundWorkerStopCheck.CancelAsync();
        }

        private void LaunchCode()
        {
            buttonStart.Enabled = false; // Disable the start button to show that the process is ongoing.
            myTimer = new System.Timers.Timer(5000); // Waste five seconds.
            myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
            myTimer.Enabled = true; // Start the timer.
        }

        void myTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            buttonStart.Enabled = true; // ReEnable the Start button to show that the process either finished or was cancelled.
        }
    }
}

コードが正しく機能していれば、ユーザーが[スタート]をクリックしてから5秒間そこに留まり、[スタート]ボタンを再度有効にするか、ユーザーが[停止]をクリックするとすぐに[スタート]ボタンが再度アクティブになります。

このコードには、処理方法がわからない2つの問題があります。

1)「myTimer_Elapsed」メソッドは、「クロススレッド操作が無効でした」ため、[スタート]ボタンを有効にしようとするとInvalidOperationExceptionが発生します。クロススレッド操作を回避するにはどうすればよいですか?

2)現在、backgroundWorkerは、キャンセルされたときにタイマーを停止するように引数をフィードする方法がわからないため、何も実行しません。

助けていただければ幸いです!

4

2 に答える 2

5

まず、「クロススレッド操作が無効でした」を回避するための問題は、コントロールでInvokeを使用することです。別のスレッドのコントロールを使用することはできません。

2番目の問題については、次のように実装します。これは、キャンセルをサポートする最小限のバックグラウンドワーカーの実装です。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication5
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // Set the background worker to allow the user to stop the process. 
            backgroundWorkerStopCheck.WorkerSupportsCancellation = true;
            backgroundWorkerStopCheck.DoWork += new DoWorkEventHandler(backgroundWorkerStopCheck_DoWork);
        }

        private void backgroundWorkerStopCheck_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                for (int i = 0; i < 50; i++)
                {
                    if (backgroundWorkerStopCheck.CancellationPending)
                    {
                        // user cancel request
                        e.Cancel = true;
                        return;
                    }

                    System.Threading.Thread.Sleep(100);
                }
            }
            finally
            {
                InvokeEnableStartButton();
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            //disable start button before launch work
            buttonStart.Enabled = false;

            // start worker
            backgroundWorkerStopCheck.RunWorkerAsync();
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            // Tell the backgroundWorker to stop process.
            backgroundWorkerStopCheck.CancelAsync();
        }

        private void InvokeEnableStartButton()
        {
            // this method is called from a thread,
            // we need to Invoke to avoid "cross thread exception"
            if (this.InvokeRequired)
            {
                this.Invoke(new EnableStartButtonDelegate(EnableStartButton));
            }
            else
            {
                EnableStartButton();
            }
        }

        private void EnableStartButton()
        {
            buttonStart.Enabled = true;
        }
    }

    internal delegate void EnableStartButtonDelegate();
}

ワーカーへの引数の受け渡しについては、メソッド内の任意のオブジェクトを渡すことができ、RunWorkerAsync()そのオブジェクトはメソッド内で返されますbackgroundWorkerStopCheck_DoWork

  ...
  backgroundWorkerStopCheck.RunWorkerAsync("hello");
  ...

  private void backgroundWorkerStopCheck_DoWork(object sender, DoWorkEventArgs e)
  {
      string argument = e.Argument as string;
      // argument value is "hello"
      ...
  }

それが役に立てば幸い。

于 2011-11-16T16:53:37.400 に答える
3

この例を試してみると、BackgroundWorkerとの間でデータをやり取りする方法がわかります。

public partial class Form1 : Form
{
    BackgroundWorker bw = new BackgroundWorker();

    public Form1()
    {
        InitializeComponent();

        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        bw.WorkerSupportsCancellation = true;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        btnStart.Enabled = false;
        btnCancel.Enabled = true;

        double[] data = new double[1000000];
        Random r = new Random();
        for (int i = 0; i < data.Length; i++)
            data[i] = r.NextDouble();

        bw.RunWorkerAsync(data);
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        btnStart.Enabled = true;
        btnCancel.Enabled = false;

        if (!e.Cancelled)
        {
            double result = (double)e.Result;
            MessageBox.Show(result.ToString());
        }
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        double[] data = (double[])e.Argument;

        for (int j = 0; j < 200; j++)
        {
            double result = 0;
            for (int i = 0; i < data.Length; i++)
            {
                if (bw.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
                result += data[i];
            }
            e.Result = result;
        }
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        bw.CancelAsync();
        btnStart.Enabled = true;
        btnCancel.Enabled = false;
    }
}
于 2011-11-16T16:58:01.433 に答える