C#中的BackgroundWorker詳解

来源:https://www.cnblogs.com/Sheldon180621/p/18245692
-Advertisement-
Play Games

在C#中,經常會有一些耗時較長的CPU密集型運算,因為如果直接在UI線程執行這樣的運算就會出現UI不響應的問題。解決這類問題的主要途徑是使用多線程,啟動一個後臺線程,把運算操作放在這個後臺線程中完成。但是原生介面的線程操作有一些難度,如果要更進一步的去完成線程間的通訊就會難上加難。 因此,.NET類 ...


在C#中,經常會有一些耗時較長的CPU密集型運算,因為如果直接在UI線程執行這樣的運算就會出現UI不響應的問題。解決這類問題的主要途徑是使用多線程,啟動一個後臺線程,把運算操作放在這個後臺線程中完成。但是原生介面的線程操作有一些難度,如果要更進一步的去完成線程間的通訊就會難上加難。

因此,.NET類庫中提供了一個叫做BackgroundWorker的類可以比較優雅的解決這類問題。雖然BackgroundWorker類使用起來比較簡單,但其中還是有一些需要註意的細節,下麵我們就通過一個簡單的Dmeo程式,來詳細介紹它的主要用法。我們在Demo中計算1到100的累加和,為了演示效果,每次計算都睡眠600毫秒,Demo的界面如下:

用法概述:

在窗體上構建一個BackgroundWorker實例,在它的DoWork事件處理函數中添加耗時的運算,然後調用它的RunWorkerAsync方法就可以了。

 1  private BackgroundWorker _demoBGWorker=new BackgroundWorker();
 2  
 3  public Form1()
 4  {
 5      InitializeComponent();
 6     
 7  }
 8  
 9  private void BGWorker_DoWork(object sender, DoWorkEventArgs e)
10  {
11      //在這裡執行耗時運算。
12      int sum = 0;
13      for(int i = 0; i < 100; i++)
14      {
15          sum += i;
16      }
17  }
18 
19 
20  private void btnStart_Click(object sender, EventArgs e)
21  {
22      _demoBGWorker.DoWork += BGWorker_DoWork;
23      _demoBGWorker.RunWorkerAsync();
24  }

以上部分,暫時沒有加入進度條的顯示邏輯,以及其餘耗時的計算,但我們可以先考慮下麵幾個問題:

  • 如果我們要想把參數傳遞給運算過程怎麼做?
  • 在運算過程中我們希望把實時的信息顯示在UI上應該怎麼辦?
  • 如果我們想要取消正在進行的運算該怎麼辦?
  • 如果運算過程出現異常我們又該如何處理?

接下來我們就一個一個的處理這些問題。

一、把參數傳遞給運算過程:

  直接把100寫死到運算過程中一般是不會用到的,常見的方式,都是客戶指定求和的範圍。因此我們在通過調用RunWorkerAsync方法啟動計算過程時,這個方法可以接受一個object類型的參數,我們可以通過它把任何數據傳遞給計算過程:

        private void BGWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //在這裡執行耗時運算。
            int endNumber = 0;
            if (e.Argument != null)
            {
                endNumber=(int)e.Argument;
            }
            int sum = 0;
            for(int i = 0; i < endNumber; i++)
            {
                sum += i;
            }
        }
        private void btnStart_Click(object sender, EventArgs e)
        {
            _demoBGWorker.DoWork += BGWorker_DoWork;

            int num = int.Parse(numericUpDown1.Value.ToString());
            //給滾動條賦值
            this.progressBar1.Maximum = num;
            _demoBGWorker.RunWorkerAsync(num);
        }

BGWork_DoWork事件處理函數通過參數e的Argument屬性傳來了我們期望的運算信息。

二、把消息傳遞給UI

  由於計算過程比較長,我們在通過進度條來顯示當前進度的同時,還希望能實時的把計算的中間愛結果顯示在UI上。當然,BackgroundWorker對這個用例也提供了很好的支持。它允許我們在執行計算的過程中給UI線程發送消息,下麵看看具體的做法:

 private void btnStart_Click(object sender, EventArgs e)
 {
     _demoBGWorker.DoWork += BGWorker_DoWork;

     int num = int.Parse(numericUpDown1.Value.ToString());
     //給滾動條賦值
     this.progressBar1.Maximum = num;
     _demoBGWorker.RunWorkerAsync(num);

     _demoBGWorker.WorkerReportsProgress = true;
     _demoBGWorker.ProgressChanged += BGWorker_ProgressChanged;
 }

  首先要把WorkerReportsProgress屬性設置為true,然後為ProgressChanged事件添加處理方法:

private void BGWorker_ProgressChanged(object sender,ProgressChangedEventArgs e)
{
    //修改進度條的顯示。
    this.progressBar1.Value=e.ProgressPercentage;
    //如果有更多的信息需要傳遞,可以使用 e.UserState傳遞一個自定義的類型。
    //這是一個object類型的對象,您可以通過它傳遞任何類型。
    //我們僅把當前sum的值通過e.UserState傳回,並通過顯示在視窗上。
    string message =e.UserState.ToString();
    this.lblSum.Text = message;
}

繼續更新BGWorker_DoWork方法:

private void BGWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bgWorker=sender as BackgroundWorker;

    //在這裡執行耗時運算。
    int endNumber = 0;

    if (e.Argument != null)
    {
        endNumber=(int)e.Argument;
    }
    int sum = 0;
    for(int i = 0; i < endNumber; i++)
    {
        sum += i;
        string message = $"Current sum is:{sum}";
        //ReportProgress 方法把信息傳遞給ProcessChanged事件處理函數。
        //第一個參數類型為 int,表示執行進度。
        //如果有更多的信息需要傳遞,可以使用ReportProgress的第二個參數。
        //這裡我們給第二個參數傳進去一條消息。
        bgWorker.ReportProgress(i, message);
        Thread.Sleep(600);
    }
}

現在,我們已經可以看到進度條和執行信息的更新了。

三、取消操作

  在執行過程中允許用戶取消當前的操作是一個基本的設計,BackgroundWorker自然有很好的支持:

 _demoBGWorker.WorkerSupportsCancellation = true;

  和WorkerReportsProgress屬性一樣,如果要支持取消操作,我們需要設置WorkerSupportsCancellation屬性為true。並且還要在BGWorker_DoWork方法中進行支持,在for迴圈中Thread.Sleep(600)後面添加代碼:

 bgWorker.ReportProgress(i, message);
 Thread.Sleep(600);

 //在操作的過程中需要檢查用戶是否取消了當前的操作。
 if (bgWorker.CancellationPending == true)
 {
     e.Cancel = true;
     break;
 }

如果檢測到用戶點擊的取消按鈕,就退出當前的計算過程,下麵是點擊取消按鈕時要調用的代碼:

private void btnCancel_Click(object sender, EventArgs e)
{
    _demoBGWorker.CancelAsync();
}

四、異常處理

  如果在計算過程中發生了異常應該怎麼處理?有沒有辦法知道計算過程已經結束?當然要有,即便是正常的結束也需要拿到計算的結果。

private void btnStart_Click(object sender, EventArgs e)
{
    _demoBGWorker.DoWork += BGWorker_DoWork;

    int num = int.Parse(numericUpDown1.Value.ToString());
    //給滾動條賦值
    this.progressBar1.Maximum = num;
    _demoBGWorker.RunWorkerAsync(num);
    _demoBGWorker.WorkerSupportsCancellation = true;
    _demoBGWorker.WorkerReportsProgress = true;
    _demoBGWorker.ProgressChanged += BGWorker_ProgressChanged;
    _demoBGWorker.RunWorkerCompleted += BGWorker_RunWorkerCompleted;
}
private void BGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //如果用戶取消了當前操作就關閉視窗。
    if (e.Cancelled)
    {
        this.Close();
    }

    //計算已經結束,需要禁用取消按鈕。
    this.btnCancel.Enabled = false;

    //計算過程中的異常會被抓住,在這裡可以進行處理。
    if (e.Error != null)
    {
        Type errorType = e.Error.GetType();
        switch (errorType.Name)
        {
            case "ArgumentNullExcepetion":
            case "MyException":
                //do something.
                break;
            default:
                //do something.
                break;
        }
    }
    //計算結果信息:e.Result
    //use it do something.
}

RunWorkerCompleted事件處理函數會在DoWork事件處理函數返回後被調用。通過它我們可以進行一些運算結束後的操作,比如禁用取消按鈕,異常處理,結果顯示等。

註意,如果想要拿到e.Result,您需要在BGWorker_DoWork方法中設置e.Result屬性,如:

e.Result=sum;

總結,BackgroundWorker類功能完善且使用簡便,非常適合處理非同步耗時的操作。

內容引用自:http://www.cnblogs.com/sparkdev/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、需求 為預防gitlab出現故障,每天定時備份,備份完成後把之前的備份文件刪除,備份成功或失敗的時候自動發送郵件提醒,這裡的gitlab為docker部署。 二、備份命令準備 1)備份命令 創建一個 gitlab_auto_backup.sh文件,文件內容 #!/bin/bash # 進入Git ...
  • 參考delphi的代碼更改為C# Delphi 檢測密碼強度 規則(仿 google) 仿 google 評分規則 一、密碼長度: 5 分: 小於等於 4 個字元 10 分: 5 到 7 字元 25 分: 大於等於 8 個字元 二、字母: 0 分: 沒有字母 10 分: 全都是小(大)寫字母 20 ...
  • 本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。 NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手 ...
  • 一、基本的.NET框架概念 .NET框架是一個由微軟開發的軟體開發平臺,它提供了一個運行時環境(CLR - Common Language Runtime)和一套豐富的類庫(FCL - Framework Class Library)。CLR負責管理代碼的執行,而FCL則提供了大量預先編寫好的代碼, ...
  • CodeWF.EventBus,一款靈活的事件匯流排庫,實現模塊間解耦通信。支持多種.NET項目類型,如WPF、WinForms、ASP.NET Core等。採用簡潔設計,輕鬆實現事件的發佈與訂閱。通過有序的消息處理,確保事件得到妥善處理。簡化您的代碼,提升系統可維護性。 ...
  • C#.NET與JAVA互通之MD5哈希V2024 配套視頻: 要點: 1.計算MD5時,SDK自帶的計算哈希(ComputeHash)方法,輸入輸出參數都是byte數組。就涉及到字元串轉byte數組轉換時,編碼選擇的問題。 2.輸入參數,字元串轉byte數組時,編碼雙方要統一,一般為:UTF-8。 ...
  • CSharpe中的IO+NPOI+序列化 文件文件夾操作 學習一下常見的文件、文件夾的操作。 什麼是IO流? I:就是input O:就是output,故稱:輸入輸出流 將數據讀入記憶體或者記憶體輸出的過程。 常見的IO流操作,一般說的是[記憶體]與[磁碟]之間的輸入輸出。 作用 持久化數據,保證數據不再 ...
  • 一:背景 1. 講故事 前些天有位朋友在微信上丟了一個崩潰的dump給我,讓我幫忙看下為什麼出現了崩潰,在 Windows 的事件查看器上顯示的是經典的 訪問違例 ,即 c0000005 錯誤碼,不管怎麼說有dump就可以上windbg開幹了。 二:WinDbg 分析 1. 程式為誰崩潰了 在 Wi ...
一周排行
    -Advertisement-
    Play Games
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式會偶發性的卡死一段時間,然後又好了,讓我幫忙看下怎麼回事?窗體類的程式解決起來相對來說比較簡單,讓朋友用procdump自動抓一個卡死時的dump,拿到dump之後,上 windbg 說話。 二:WinDbg 分析 1. 主線程在做什麼 要想 ...
  • 功能說明 使用ListView時,希望可以在單元格顯示圖片或其他控制項,發現原生的ListView不支持,於是通過拓展,實現ListView可以顯示任意控制項的功能,效果如下: 實現方法 本來想著在單元格裡面實現控制項的自繪的,但是沒找到辦法,最後是通過在單元格的錶面顯示對應控制項的,浮於錶面達到目的。 實 ...
  • 由於.NET Framework 4.0 是比較古老的版本,只有New Relic 7.0以下的版本才會支持.NET Framework 4.0的引用程式。 Technical support for .NET Framework 4.0 or lower 你可以參考這個官方Install New ...
  • 前言 隨著 DEV24.1.3 的發佈,XAF Blazor 中的屬性編輯器(PropertyEditor)也進行了很大的改動,在使用體驗上也更接近 WinForm 了,由於進行了大量的封裝,理解上沒有 WinForm 直觀,所以本文通過對屬性編輯器的原理進行解析,並對比新舊版本中的變化,使大家能夠 ...
  • OPC基金會提供了OPC UA .NET標準庫以及示常式序,但官方文檔過於簡單,光看官方文檔和示常式序很難弄懂OPC UA .NET標準庫怎麼用,花了不少時間摸索才略微弄懂如何使用,以下記錄如何從一個控制台程式開發一個OPC UA伺服器。 安裝Nuget包 安裝OPCFoundation.NetSt ...
  • 今天在技術群里,石頭哥向大家提了個問題:"如何在一個以System身份運行的.NET程式(Windows Services)中,以其它活動的用戶身份啟動可互動式進程(桌面應用程式、控制台程式、等帶有UI和互動式體驗的程式)"? 我以前有過類似的需求,是在GitLab流水線中運行帶有UI的自動化測試程 ...
  • .Net 中提供了一系列的管理對象集合的類型,數組、可變列表、字典等。從類型安全上集合分為兩類,泛型集合 和 非泛型集合,傳統的非泛型集合存儲為Object,需要類型轉。而泛型集合提供了更好的性能、編譯時類型安全,推薦使用。 ...
  • 在以前我做程式的時候,一般在登錄視窗裡面顯示程式名稱,登錄視窗一般設置一張背景圖片,由於程式的名稱一般都是確定的,所以也不存在太大的問題,不過如果客戶定製不同的系統的時候,需要使用Photoshop修改下圖層的文字,再生成圖片,然後替換一下也可以了。不過本著減少客戶使用繁瑣性,也可以使用空白名稱的通... ...
  • 一:背景 1. 講故事 在dump分析的過程中經常會看到很多線程卡在Monitor.Wait方法上,曾經也有不少人問我為什麼用 !syncblk 看不到 Monitor.Wait 上的鎖信息,剛好昨天有時間我就來研究一下。 二:Monitor.Wait 底層怎麼玩的 1. 案例演示 為了方便講述,先 ...
  • 目錄前言學習參考過程總結: 前言 做個自由仔。 學習參考 ChatGpt; https://www.cnblogs.com/zhili/p/DesignPatternSummery.html(大佬的,看了好多次) 過程 原由: 一開始只是想查查鏈式調用原理,以為是要繼承什麼介面,實現什麼方法才可以實 ...