星期日, 2月 19, 2017

[C#] BackgroundWorker 範例3

閱讀該篇 MSDN 文章 - 逐步解說:實作使用背景作業的表單 並記錄,該文章是利用 Fibonacci 來產生耗時操作,藉此來學習 BackgroundWorker

WinForm layout

[C#] BackgroundWorker - Fibonacci-1

namespace BackgroundWorkerFibonacci
{
    public partial class frmFibonacci : Form
    {
        public frmFibonacci()
        {
            InitializeComponent();
            InitControl();
        }

        private void InitControl()
        {
            numericUpDown1.Minimum = 1;
            numericUpDown1.Maximum = 91;
            numericUpDown1.Value = 1;

            btnCancelAsync.Enabled = false;

            lblResult.Text = "(no result)";
            lblResult.TextAlign = ContentAlignment.MiddleCenter;

            progressBar1.Step = 2;

            backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.WorkerSupportsCancellation = true;
            backgroundWorker1.DoWork += BackgroundWorker1_DoWork;
            backgroundWorker1.RunWorkerCompleted += BackgroundWorker1_RunWorkerCompleted;
            backgroundWorker1.ProgressChanged += BackgroundWorker1_ProgressChanged;
        }

        // This event handler updates the progress bar.
        private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }

        // This event handler deals with the results of the background operation.
        private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // First, handle the case where an exception was thrown.
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else if (e.Cancelled)
            {
                // Next, handle the case where the user canceled the operation.
                // Note that due to a race condition in the DoWork event handler, the Cancelled flag may not have been set, even though CancelAsync was called.
                lblResult.Text = "Canceled";
            }
            else
            {
                // Finally, handle the case where the operation succeeded.
                // RunWorkerCompletedEventArgs.Result 是接收 DoWorkEventArgs.Result 的值
                lblResult.Text = e.Result.ToString();
            }

            // Enable the UpDown control.
            numericUpDown1.Enabled = true;
            // Enable the Start button.
            btnStartAsync.Enabled = true;
            // Disable the Cancel button.
            btnCancelAsync.Enabled = false;
        }

        // This event handler is where the actual,potentially time-consuming work is done.
        private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            // Get the BackgroundWorker that raised this event.
            BackgroundWorker worker = sender as BackgroundWorker;

            // Assign the result of the computation to the Result property of the DoWorkEventArgs object. This is will be available to the RunWorkerCompleted eventhandler.
            // DoWork Event 內的 DoWorkEventArgs.Argument 是接收 RunWorkerAsync(參數),要注意轉型問題
            e.Result = ComputeFibonacci((int)e.Argument, worker, e);
        }

        private int numberToCompute = 0;
        private int highestPercentageReached = 0;

        // This is the method that does the actual work. For this example, it computes a Fibonacci number and reports progress as it does its work.
        long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e)
        {
            // The parameter n must be >= 0 and <= 91.
            // Fib(n), with n > 91, overflows a long.
            if ((n < 0) || (n > 91))
                throw new ArgumentException("value must be >= 0 and <= 91", "n");

            long result = 0;

            // Abort the operation if the user has canceled.
            // Note that a call to CancelAsync may have set 
            // CancellationPending to true just after the
            // last invocation of this method exits, so this 
            // code will not have the opportunity to set the 
            // DoWorkEventArgs.Cancel flag to true. This means
            // that RunWorkerCompletedEventArgs.Cancelled will
            // not be set to true in your RunWorkerCompleted
            // event handler. This is a race condition.
            if (worker.CancellationPending)
            {
                e.Cancel = true;
            }
            else
            {
                if (n < 2)
                {
                    result = 1;
                }
                else
                {
                    result = ComputeFibonacci(n - 1, worker, e) + ComputeFibonacci(n - 2, worker, e);
                }

                // Report progress as a percentage of the total task.
                int percentComplete = (int)((float)n / (float)numberToCompute * 100);
                if (percentComplete > highestPercentageReached)
                {
                    highestPercentageReached = percentComplete;
                    worker.ReportProgress(percentComplete);
                }
            }

            return result;
        }

        private void btnStartAsync_Click(object sender, EventArgs e)
        {
            // Reset the text in the result label.
            lblResult.Text = String.Empty;

            // Disable the UpDown control until the asynchronous operation is done.
            numericUpDown1.Enabled = false;

            // Disable the Start button until the asynchronous operation is done.
            btnStartAsync.Enabled = false;

            // Enable the Cancel button while the asynchronous operation runs.
            btnCancelAsync.Enabled = true;

            // Get the value from the UpDown control.
            numberToCompute = (int)numericUpDown1.Value;

            // Reset the variable for percentage tracking.
            highestPercentageReached = 0;

            // Start the asynchronous operation.
            backgroundWorker1.RunWorkerAsync(numberToCompute);
        }

        private void btnCancelAsync_Click(object sender, EventArgs e)
        {
            // Cancel the asynchronous operation.
            backgroundWorker1.CancelAsync();

            // Disable the Cancel button.
            btnCancelAsync.Enabled = false;
        }
    }
}
  • 學習重點 1:參數傳遞
  1. DoWork Event 內的 DoWorkEventArgs.Result property (Code 74 行)可以在 RunWorkerCompleted Event 內的 RunWorkerCompletedEventArgs.Result property 中抓出 (Code 55 行)
  2. RunWorkerAsync(參數) (Code 147 行) 可以在 DoWork Event 內的 DoWorkEventArgs.Argument property (Code 74 行)抓出,要注意轉型問題
  • 學習重點 2:競爭情況,擷取文章內容 Code 90 - 98 行說明
Abort the operation if the user has canceled.
Note that a call to CancelAsync may have set CancellationPending to true just after the last invocation of this method exits, so this code will not have the opportunity to set the DoWorkEventArgs.Cancel flag to true. This means that RunWorkerCompletedEventArgs.Cancelled will not be set to true in your RunWorkerCompleted event handler. This is a race condition.
  • 學習重點 3:DoWork Event,擷取文章內容
DoWork 事件處理常式並未直接參考 backgroundWorker1 執行個體,因為這會讓此事件處理常式與 BackgroundWorker 的特定執行個體結合。 請以引發這個事件的 BackgroundWorker 參考代替,此參考會從 sender 參數還原。 當表單裝載了一個以上的 BackgroundWorker 時,這點非常重要。 另外您還必須注意,不要在 DoWork 事件處理常式中操作任何使用者介面物件。 相反地,請透過 BackgroundWorker 事件,與使用者介面通訊。
執行結果

[C#] BackgroundWorker - Fibonacci-2

[C#] BackgroundWorker - Fibonacci-3

[C#] BackgroundWorker - Fibonacci-4

沒有留言:

張貼留言