星期五, 2月 17, 2017

[C#] BackgroundWorker 範例1

閱讀該篇 MSDN 文章 - Walkthrough: Multithreading with the BackgroundWorker Component (C#) 並記錄,該文章是利用讀取 txt 檔案並統計其關鍵字,藉此介紹 BackgroundWorker 功能

Solution 檔案

WalkthroughBackgroundWorker-1

WinForm Layout

WalkthroughBackgroundWorker-2

Form1.cs
using System.IO;

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

        private void Form1_Load(object sender, EventArgs e)
        {
            // 屬性設定
            // 使用 ReportProgress() 必須把 WorkerReportsProgress 設為 true
            backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.WorkerSupportsCancellation = true;

            // 事件註冊
            backgroundWorker1.RunWorkerCompleted += BackgroundWorker1_RunWorkerCompleted;
            backgroundWorker1.ProgressChanged += BackgroundWorker1_ProgressChanged;
            backgroundWorker1.DoWork += BackgroundWorker1_DoWork;

            txtSourceFile.Text = @"D:\Sample.txt";
            txtCompareString.Text = "21934";
            CounterReset();
            ButtonControl(true);
        }

        private void StartThread()
        {
            CounterReset();

            Words WC = new Words();
            WC.SourceFile = txtSourceFile.Text.Trim();
            WC.CompareString = txtCompareString.Text.Trim();

            // RunWorkerAsync() 會觸發 DoWork Event
            backgroundWorker1.RunWorkerAsync(WC);
        }

        private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            // This event handler is where the actual work is done.
            // This method runs on the background thread.  
            BackgroundWorker worker = (BackgroundWorker)sender;
            Words WC = (Words)e.Argument;
            WC.CountWords(worker, e);
        }

        private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // This method runs on the main thread. 
            // 更新統計資訊
            Words.CurrentState state = (Words.CurrentState)e.UserState;
            txtLinesCounted.Text = state.LinesCounted.ToString();
            txtWordsCounted.Text = state.WordsMatched.ToString();
        }

        private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // This event handler is called when the background thread finishes.  
            // This method runs on the main thread.  
            if (e.Error != null)
                MessageBox.Show("錯誤訊息:" + e.Error.Message);
            else if (e.Cancelled)
                MessageBox.Show("字數統計取消");
            else
                MessageBox.Show("完成字數統計");

            ButtonControl(true);
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (File.Exists(txtSourceFile.Text.Trim()) == false)
            {
                MessageBox.Show("該檔案路徑不存在,無法進行解析", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            ButtonControl(false);
            StartThread();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            // 取消非同步操作  
            backgroundWorker1.CancelAsync();
            CounterReset();
            ButtonControl(true);
        }

        private void ButtonControl(bool state)
        {
            btnStart.Enabled = state;
            btnCancel.Enabled = !state;
        }

        private void CounterReset()
        {
            txtWordsCounted.Text = "0";
            txtLinesCounted.Text = "0";
        }

        private void btnFileSelect_Click(object sender, EventArgs e)
        {
            OpenFileDialog fd = new OpenFileDialog();
            fd.Filter = "txt 檔案 (*.*)|*.txt";
            fd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            if (fd.ShowDialog() == DialogResult.Cancel) return;
            txtSourceFile.Text = fd.FileName;
        }
    }
}
Words.cs
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.IO;
using System.Threading;

namespace WalkthroughBackgroundWorker
{
    public class Words
    {
        // Object to store the current state, for passing to the caller.  
        public class CurrentState
        {
            public int LinesCounted { get; set; }
            public int WordsMatched { get; set; }
        }

        public string SourceFile { get; set; }
        public string CompareString { get; set; }
        private int WordCount { get; set; }
        private int LinesCounted { get; set; }

        public void CountWords(BackgroundWorker worker, DoWorkEventArgs e)
        {
            if (string.IsNullOrEmpty(CompareString))
                throw new Exception("沒有指定搜尋字串");

            // 變數初始化
            CurrentState state = new CurrentState();
            string line = string.Empty;

            // 利用 StreamReader 讀取 txt 檔案 
            using (StreamReader sr = new StreamReader(SourceFile))
            {
                // Process lines while there are lines remaining in the file. 
                while (!sr.EndOfStream)
                {
                    // 使用者取消後,就不繼續讀取
                    if (worker.CancellationPending == true)
                    {
                        e.Cancel = true;
                        break;
                    }

                    line = sr.ReadLine();
                    WordCount += CountInString(line, CompareString);
                    // 紀錄行數
                    LinesCounted++;

                    state.LinesCounted = LinesCounted;
                    state.WordsMatched = WordCount;
                    worker.ReportProgress(0, state);
                    // 處理每一行都停止 50 毫秒,方便觀察執行結果
                    Thread.Sleep(50);
                }

                // Report the final count values.  
                state.LinesCounted = LinesCounted;
                state.WordsMatched = WordCount;
                worker.ReportProgress(100, state);
            }
        }

        private int CountInString(string SourceString,string CompareString)
        {
            // This function counts the number of times 
            // a word is found in a line.  
            if (SourceString == null)
            {
               return 0;
            }

            // Regex.Escape:以逸出碼取代 (\、*、+、?、|、{、[、(、)、^、$、.、# 和泛空白字元) 等字元。 這樣會指示規則運算式引擎將這些字元解譯為常值,而非解譯為中繼字元。
            string EscapedCompareString = Regex.Escape(CompareString);

            // To count all occurrences of the string, even within words, remove both instances of @"\b" from the following line. 
            // 下述兩種寫法都可以使用
            string pattern = @"\b" + EscapedCompareString + @"\b";
            // string pattern = $"\\b{EscapedCompareString}\\b";

            Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
            MatchCollection matches = regex.Matches(SourceString);
            return matches.Count;
        }
    }
}
下面這段 Code,用途應該是每 20 毫秒才透過 ReportProgress() 觸發 ProgressChanged Event,避免觸發太過頻繁,不過個人感覺用 Thread.Sleep 觀察執行結果,效果比較好,所以就把它拿掉
int compare = DateTime.Compare(  
DateTime.Now, lastReportDateTime.AddMilliseconds(elapsedTime));  
if (compare > 0)  
{  
    // ...................................
}  
統計完成

WalkthroughBackgroundWorker-3

執行過程中,取消統計

WalkthroughBackgroundWorker-4

沒有留言:

張貼留言