国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

【C#】分享帶等待窗體的任務執行器一枚

2019-11-17 02:26:53
字體:
來源:轉載
供稿:網友
【C#】分享帶等待窗體的任務執行器一枚

-------------201504161039更新-------------

更新內容:

  1. IWaitForm接口刪除System.Windows.Forms.DialogResult DialogResult屬性。即隱藏等待窗體的方式不再分為設置DialogResult調用Hide()兩種,改為僅調用Hide()一種,簡化設計。由于Hide()屬于訪問控件,執行器需根據自身是否會跨線程調用該方法而做出相應處理
  2. WaitUI增加私有方法HideWaitForm,用于隱藏等待窗體(由于會在后臺線程調用該方法,故內部有跨線程處理),替代原來的設置DialogResult的做法
  3. WaitForm的FormClosing事件由注冊該事件改為重寫OnFormClosing方法,對阻止窗體關閉的條件增加了Visible,即當窗體處于可見時,才會阻止窗體關閉和觸發UserCancelling事件,這是為了更準確的區分是執行器調用Hide()隱藏等待窗體,還是用戶關閉等待窗體,僅通過e.CloseReason是不可靠的,因為當用戶點過關閉按鈕后,e.CloseReason就會是UserClosing,稍后執行器在調用Hide隱藏窗體時,仍然會進入OnFormClosing,此時e.CloseReason仍然是UserClosing,就會再次觸發UserCancelling事件,雖然沒什么影響,但實屬不應該,加了Visible的話,執行器Hide窗體后,Visible就為false,就不會再次觸發UserCancelling事件。當然,仍然建議自定義等待窗體屏蔽關閉按鈕,讓用戶只能通過點擊取消控件來取消任務,就沒那么多事了。但不建議通過把ControlBox=false來整個隱藏右上角那仨按鈕,因為我始終認為要給用戶最小化的權利,我作為用戶使用其它軟件的時候,是很痛恨這種限制的
  4. 等待窗體的【取消】按鈕單擊后不再將Enabled置為false。原因是在基于BackgroundWorker的方案中,等待窗體Hide后有可能再次ShowDialog,也就是再次執行任務時依然要保證可取消
  5. 將WaitFormNullException異常的定義移至WaitForm.cs文件中(原先在WaitUI.cs中)。原因是等待窗體相關的東西應該與執行器保持獨立

-------------20150415原文(已更新)-------------

適用環境:.net 2.0+的Winform項目。

先解釋一下我所謂的【帶等待窗體的任務執行器】是個什么鬼,就是可以用該類執行任意耗時方法(下文將把被執行的方法稱為任務任務方法),執行期間會顯示一個模式等待窗體,讓用戶知道任務正在得到執行,程序并沒有卡死。先看一下效果:

功能:

  • 等待窗體可以使用執行器自帶的默認窗體(就上圖的樣子),嫌丑你也可以使用自己精心設計的窗體,甚至基于DevexPRess、C1等第三方漂亮窗體打造也是完全可以的
  • 在任務中可以更新等待窗體上的Label、ProgressBar之類的控件以提供進度反饋。懶得反饋的話,就默認“請稍候...”+Marquee式滾動
  • 如果任務允許被終止,用戶可以通過某些操作終止任務執行(例如點擊上圖中的【取消】按鈕);如果不允許,你可以把取消按鈕隱藏了,或者在任務中不響應用戶的終止請求就好
  • 任務的執行結果(包括ref/out參數)、是否出現異常、是否被取消等情況都可以得到

原理:

  • 調用任務所屬委托的BeginInvoke,讓任務在后臺線程執行,隨即在UI線程(通常就是主線程)調用等待窗體的ShowDialog彈出模式窗體,讓用戶知道任務正在執行的同時阻止用戶進行其他操作。由于任務和等待窗體分別在不同的線程跑,所以等待窗體不會被卡住
  • 任務執行期間可以通過執行器提供的一組屬性和方法操作等待窗體上的控件,這組屬性和方法內部是通過調用等待窗體的Invoke或BeginInovke對控件進行操作,實現跨線程訪問控件
  • 任務執行期間用戶可以通過點擊等待窗體上的【取消】按鈕(如果你讓它顯示的話)或點擊右上角關閉按鈕發出終止任務的請求(等待窗體會攔截關閉操作),其結果是執行器的UserCancelling屬性會置為true,所以在任務中可以訪問該屬性得知用戶是否請求了取消操作,如果你同意終止的話,需設置執行器的Cancelled=true,并隨即return出任務方法
  • 任務執行完后(無論成功、異常、取消)會自動進入異步回調方法,回調方法中會首先訪問Cancelled獲知任務是否已取消,如果已取消,則直接return出回調方法;如果未取消,則調用任務所屬委托的EndInvoke得到任務執行結果或異常。最后不管取消與否,finally塊中會調用HideWaitForm(),以確保關閉等待窗體
  • 等待窗體關閉后,執行器會繼續執行ShowDialog后面的語句。如果任務已取消,則拋出特定異常報告調用者;如果任務存在異常,則拋出該異常;以上情況都不存在的話,返回任務結果

如述,功能簡單,實現容易,我只是把這種需求通用化了一下,讓還沒有類似輪子的朋友可以拿去就用。另外還有個基于BackgroundWorker的實現方式,我可能會在下一篇文章分享。

先看一下大致的使用示例:

//WaitUI就是執行器private void button1_Click(object sender, EventArgs es){    //可檢測執行器是否正在執行另一個任務。其實基本不可能出現IsBusy=true,因為執行器工作時,用戶做不了其它事    //老實說這個IsBusy要不要公開我還糾結了一下,因為公開了沒什么用,但也沒什么壞處,因為setter是private的    //Whatever~最后我還是選擇公開,可能~因為愛情    //if (WaitUI.IsBusy) { return; }    try    {        //WaitUI.RunXXX方法用于執行任務        //該方法的返回值就是任務的返回值        //任務拋出的異常會通過RunXXX方法拋出        //WaitUI.RunAction(Foo, 33, 66);                                      //執行無返回值的方法        int r = WaitUI.RunFunc(Foo, 33, 66);                                  //執行有返回值的方法        //object r = WaitUI.RunDelegate(new Func<int, int, int>(Foo), 33, 66);//執行委托        //WaitUI.RunAction(new MyWaitForm(), Foo);//指定自定義等待窗體執行任務,幾個RunXXX方法都有可指定自定義窗體的重載        MessageBox.Show("任務完成。" + r);    }    catch (WorkCancelledException)//任務被取消是通過拋出該異常來報告    {        MessageBox.Show("任務已取消!");    }    catch (Exception ex)//任務拋出的異常    {        MessageBox.Show("任務出現異常!" + ex.Message);    }}//耗時任務。因為該方法會在后臺線程執行,所以方法中不可以有訪問控件的代碼int Foo(int a, int b){    //可以通過執行器的一系列公開屬性和方法間接操作等待窗體的UI元素    WaitUI.CancelControlVisible = true;//設置取消任務的控件的可見性,即是否允許用戶取消任務(默認是false:不可見)    WaitUI.BarStyle = ProgressBarStyle.Continuous;//設置滾動條樣式(默認是Marquee:循環梭動式)    WaitUI.BarMaximum = 100;      //設置滾動條值上限(默認是100)    WaitUI.BarMinimum = 0;        //設置滾動條值下限(默認是0)    WaitUI.BarStep = 1;           //設置滾動條步進幅度(默認是10)    WaitUI.BarVisible = true;     //設置滾動條是否可見(默認是true:可見)    int i;    for (i = a; i < b; i++)    {        if (WaitUI.UserCancelling)//響應用戶的取消請求        {            WaitUI.Cancelled = true;//告訴執行器任務已取消            return 0;        }        //可以拋個異常試試        //if (i == 43) { throw new NotSupportedException("異常測試"); }        //可以隨時再次操作等待窗體的各種UI元素        //if (i % 10 == 0) { WaitUI.CancelControlVisible = false; }   //隱藏取消控件        //else if (i % 5 == 0) { WaitUI.CancelControlVisible = true; }//顯示取消控件        WaitUI.WorkMessage = "正在XXOO,已完成 " + i + " 下..."; //更新進度描述        WaitUI.BarValue = i;//更新進度值        //WaitUI.BarPerformStep();//步進進度條        Thread.Sleep(50);    }    return i;}
使用示例

看完示例,熟悉套路的你可能都已經能洞悉實現細節了,不過作為方案分享文章,我還是照章講一下使用說明先,后面再掰扯設計說明,先看類圖:

使用說明:

  1. WaitUI通過RunAction、RunFunc、RunDelegate這3個基本方法和它們的重載執行任務,看名字就知道,它們依次是執行無返回值方法、有返回值方法和自定義委托,每個方法都有不指定等待窗體指定等待窗體兩種重載形態,不指定時就使用方案自帶的WaitForm作為等待窗體。自定義等待窗體需實現IWaitForm接口,詳情在后面的設計說明部分有說。這里就表示等待窗體是在執行任務時才傳進去的,任務執行完成后,WaitUI會銷毀等待窗體,這是為了讓WaitUI作為一個靜態類,盡量短暫的持有對象,節約內存。所以如果傳入的是自定義等待窗體的變量,請注意不要在WaitRun之后再次使用該變量,因為它已經被銷毀,推薦的做法是直接在RunXXX中new一個自定義等待窗體。當然如果不嫌棄自帶等待窗體,直接就不用傳入,自然不會有這個問題。前兩種方法是泛型方法,根據Action和Func這倆泛型委托重載,這倆委托支持到最多16個參數,但為了節約篇幅,方案中只重載了0~8個參數的情況,用戶可以根據需要增加重載。它倆可以執行任意不多于8個參數的有返回或無返回方法,得益于編譯器的智能推斷,使用時非常方便,直接RunAction(Foo, arg1, arg2, ...)就好了,根本不用糾結到底要使用哪個重載。對于RunDelegate方法,接受的是一個委托實例,也就是不能直接傳入方法,必須要用委托把方法套上才行。任何委托都可以傳入,所以RunDelegate是最應萬變的方法,當你的方法存在ref/out參數,或者參數個數變態到超過16個時,你還可以也只可以選用RunDelegate。但有個限制,委托有且只有綁定一個方法,RunXXX拒絕執行委托鏈
  2. RunFunc和RunDelegate方法有返回值,前者的返回類型與任務方法的返回類型一致,后者則是object。它倆的返回值就是任務方法的返回值。同樣,任務拋出的異常一樣會在3種RunXXX方法中拋出來,等于用RunXXX執行任務直接調用任務相比,除了寫法上有所不同:
    int a = WaitUI.RunFunc(Foo,33);int b = Foo(33);

    調用體驗是一樣一樣的,所以你拿去就可以用,不需要對任務方法做什么修改,帶個套就完事兒,除非任務方法存在下面這種情況

  3. 原理中說過,RunXXX方法實際上是調用任務所屬委托的BeginInvoke方法,也就是異步執行任務,也就是任務會在另一個線程執行。所以任務中不能訪問控件,這恐怕是該方案最大的不便,但確實原理所限,所以如果你的任務有訪問控件的代碼,還得做出改動才行。要問為什么非得讓任務在后臺,而等待窗體在前臺,不可以調換過來嗎?那樣不就沒這個不便了嗎?那是因為等待窗體如果不在主線程ShowDialog,它就達不到模式的效果,用戶仍然可以唱歌跳舞,這恐怕是你不愿意的
  4. 任務中可以通過WaitUI的一組屬性和方法(WorkMessage、BarValue、BarPerformStep等)更新等待窗體中的文本呈現控件和進度指示控件(不限于Label和ProgressBar,取決于等待窗體的設計),用來向用戶報告任務執行進度。當然不想做任何報告也可以,就讓用戶面對一個“請稍候...”和循環滾動條也無不可,具體文字和滾動條樣式取決于等待窗體的默認設置
  5. WaitUI有個CancelControlVisible屬性,可以設置為true/false控制等待窗體上是否顯示【取消】按鈕之類的控件(不限于Button,取決于等待窗體的設計,所以下文不說取消按鈕,說取消控件)。這里需要詳細說一下該方案的取消任務的機制,其實與BackgroundWorker的機制一致(好吧是我借鑒了它),熟悉bgw的老鳥請略過。顯示取消控件只代表用戶可以請求終止任務,至于你(或者說任務)是否響應這個請求(同意終止與否)是另一回事。什么意思,就是用戶點擊取消控件后,不是說任務就會自動終止了~憑什么會終止嘛對吧,任務在線程池,又不可能Abort,所以任務是否終止完全取決你在任務代碼中的處理,比如你在任務中段就來個return或throw ex,這個就叫終止,任由代碼執行下去,就叫做沒終止,所以用戶請求終止與任務是不是真的終止了沒有必然聯系。說這么多是什么意思,就是如果你要讓用戶看到取消控件,那么你就應該響應用戶的請求,反過來如果不想任務被終止,那么就別讓用戶有發起請求的可能,當然這是與技術無關的純人機交互理念的東西,沒有對錯,反正我是建議不要欺騙用戶,下面說說如何響應終止請求。當用戶發起終止請求后,WaitUI的UserCancelling會變為true,在任務中你可以根據這個值來做出終止任務的處理,但是在終止之前,還得麻煩你設置一個標記,千萬別忘記,就是讓WaitUI.Cancelled = true,這等于告訴執行器任務確實終止了,在設置完標記后,最好緊跟終止代碼,不要再做其它事,讓Cancelled與事實一致。執行器中根據Cancelled來獲知任務是否已終止,進而做出相應的處理和返回。為什么不根據UserCancelling而是Cancelled相信你已經明白了,前者是用戶的意愿,后者是開發者的決定,當然是決定靠譜?;氐紺ancelControlVisible屬性,這個屬性建議在任務方法頂部就設置,因為一個任務是否可終止應該是確定的,通常來說,循環類任務是可以終止的,例如批量處理圖片,一圈處理一張,那這種任務是可以也應該允許用戶終止的;而非循環類任務,或者原子性比較強的任務,開始了就只能等待結果或報錯,這種任務一方面可能就不允許用戶終止,另一方面則是想終止都終止不了(比如WebRequest.GetResponse、SqlCommand.ExecuteNonQuery之類),這種任務最好就不提供取消控件給用戶。不過CancelControlVisible也像WorkMessage之類的屬性一樣,是可以在任務中隨時+反復設置的,所以你的任務可能有些階段可被終止,有時則不允許終止,開開合合都是可以的,as you wish
  6. RunXXX有3種執行結果:①成功執行任務,返回任務返回值~如果任務有返回值的話;②任務產生異常,RunXXX會原樣拋出該異常;③任務被終止,拋出WorkCancelledException異常(后面有關于為什么選擇拋異常這種方式的說明)。你自行根據不同結果做相應處理
  7. 對于有ref/out參數的任務方法,如果你想在任
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 景泰县| 建始县| 德兴市| 嵊州市| 上蔡县| 香河县| 利辛县| 高雄市| 玛沁县| 包头市| 敦煌市| 益阳市| 丰县| 华阴市| 嘉鱼县| 华池县| 哈密市| 左权县| 长岛县| 乌审旗| 温宿县| 屏南县| 阜阳市| 平遥县| 临沂市| 鄂尔多斯市| 灌云县| 久治县| 昌都县| 新余市| 新郑市| 油尖旺区| 临海市| 邢台市| 常山县| 鄂州市| 逊克县| 灵武市| 香港| 浦县| 南通市|