winfrom程序中很多地方需要用到異步操作,比如用戶的登陸,在登陸的時候,登陸界面是鎖定了,不允許任何的操作,但如果用戶這時想取消登錄,出來關閉程序外,就沒有其他方式了。好在可以通過異步操作來實現登錄的時候,讓用戶點擊取消按鈕來達到取消登錄的目的。
1、通過線程來實現異步操作:
private void Button_Click(object sender, RoutedEventArgs e) { WaiteWin ww = new WaiteWin(); new Thread(o => { TestTask(10000);//假設點擊按鈕后的操作需要10秒來完成 //下面通過一個異步委托來執行指定的操作OutputInfo,這個操作有兩個參數<Button, WaiteWin>,傳遞的實參為 sender, ww //這個操作是在前面的TestTask執行完成后執行的操作 Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<Button, WaiteWin>(OutputInfo), sender, ww); }) {//需要注意的就是IsBackground默認為false,也就是該線程對調用它的線程不產生依賴,當調用線程退出時該線程也不會結束。 //因此需要將IsBackground設置為true以指明該線程是后台線程,這樣當主線程退出時該線程也會結束。 IsBackground = true } .Start();//這一步以后的操作是在TestTask執行的同時執行的操作 ww.ShowDialog(); }
上面的代碼實現了這樣一個功能,就是在點擊按鈕實現操作TestTask的時候,這個操作可能會花費很長一段時間,這時通過彈出一個提示用戶等待的框框ww,來提高用戶體驗。
在這個按鈕事件中,TestTask方法和start()后面的代碼是同時執行的,當testtask里面的操作完成后,程序進入outputinfo方法中去關閉提示框。
2、線程池來實現異步操作
線程池的實現方式和上面的差不多
private void Button_Click_1(object sender, RoutedEventArgs e) { WaiteWin ww = new WaiteWin(); ThreadPool.QueueUserWorkItem (s=>{ TestTask(5000); Dispatcher.BeginInvoke(DispatcherPriority.Normal,new Action<Button,WaiteWin>(OutputInfo), sender, ww); }); ww.ShowDialog(); }
3、使用async/await進行異步操作
private async void Button_Click_2(object sender, RoutedEventArgs e) { WaiteWin ww = new WaiteWin(); Task t = new Task(() => { TestTask(5000); }); t.Start();
ww.ShowDialog();
await t;
ww.closeMe(); }
這種方式和上面的兩種方式有一個很大的不同之處,就是程序在執行到await t 的時候,就返回了,因此要和testtask操作同時執行的操作就必須放在await t這條語句之前,在這條語句之后的操作都會在執行完testtask后才執行,因此用這種方式就不能用來彈出提示框了,因為彈出了提示框需要用戶手動的去關閉才會繼續執行后面的操作,否則程序不會執行await t以后的操作,即不能實現自動關閉提示框,因為程序一直在await t這個地方等着的。還有一點要注意的是,要使用await這個功能,必須是在.NET 4以后的版本才可以用,如果是之前的版本是沒法用的。
但是可以這樣做
private void Button_Click_2(object sender, RoutedEventArgs e) { WaiteWin ww = new WaiteWin(); Task t = new Task(() => { TestTask(5000); }); t.Start(); //在這里可以執行其他需要與TestTask同時執行的操作 t.Wait(); //或者用這個Task.WaitAll(t); ww.ShowDialog(); }
總結:三種方式,在程序執行testtask的時候,用戶都可以對界面做其他操作,比如點擊按鈕什么的,但是,在testtask方法以及這個方法所調用的其他方法中,不能使用界面的任何一個元素,哪怕是獲取文本的值也不行,否則會出錯。第三種方式如果是彈出的一個提示框,那么在程序執行完testtask的時候,它不會自動的去關閉提示框,因為線程一直在t.wait()這個地方等着,等着提示框被關閉,所以除非是手動去關閉,否則程序會一直等待下去。
4.下面介紹的方式,可用於動態的改變界面的元素,比如,點擊一個按鈕,這個按鈕執行的任務可能會花很長一段時間,這時需要一個進度條來告訴用戶程序正在進行中,如果是用上面的線程之類的異步,不太好讓進度條動態的改變的,但是用下面的方式就能做到。這里為了簡單起見,直接用按鈕的文本來顯示程序正在執行。
在按鈕觸發事件中,定義一個BackgroundWorker對象。其中DoWork方法表示需要執行很長一段時間的操作
private void Button_Click(object sender, RoutedEventArgs e) { BackgroundWorker bw = new BackgroundWorker(); bw.WorkerReportsProgress = true; bw.WorkerSupportsCancellation = true; bw.DoWork += DoWork; bw.ProgressChanged += ProgressChanged; bw.RunWorkerCompleted += RunWorkerCompleted; if (bw.IsBusy != true) { bw.RunWorkerAsync(); } }
private void DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; int i = 0; while (i < 100) { worker.ReportProgress(i); System.Threading.Thread.Sleep(100); if (i == 60) break; i++; } e.Cancel = true; }
在DoWork方法中,用線程的睡眠來模擬費時的操作。worker.ReportProgress(i);這條語句會觸發ProgressChanged事件,在ProgressChanged事件中就可以修改按鈕的文本並且在修改完后,用戶能立即看到修改的結果。
private void ProgressChanged(object sender, ProgressChangedEventArgs e) { btn1.Content = e.ProgressPercentage.ToString(); }
在DoWork方法中,設置e.Cancel = true;主要是為了在DoWork方法執行完成后,RunWorkerCompleted中來判斷執行的結果,這里設置的e.Cancel值會傳遞到RunWorkerCompleted事件中
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled == true) MessageBox.Show("ss"); else if (e.Error != null) MessageBox.Show(e.Error.Message); else MessageBox.Show("0"); }
點擊按鈕后,就會看到按鈕的文本從1變到60,而不是等程序執行完了,直接從1變為60.這對於費時的操作來說,就很有必要了.
同樣的,實現進度條,下面用多線程的方式
private void Button_Click(object sender, RoutedEventArgs e) { setContent(); } private void setContent() { new Thread(o => { content++; Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(ffff)); }) { IsBackground = true }.Start(); } private void ffff() { m123.Content = content.ToString();//這里的m123是一個按鈕,可以將m123換為進度條,這樣就是進度條的顯示了 if(content<500) setContent(); }
5、委托的異步調用
首先是要定義一個委托:delegate int Parmart(object data);這里的int對應的是委托將要執行的方法的返回值,如果這個方法沒有返回值,則為void,而參數data對應的也是這個方法的參數。
delegate int Parmart(object data); private void cmdThree2(object sender, RoutedEventArgs e) { Parmart mydelegate = new Parmart(ModifyBtnContent); IAsyncResult result =mydelegate.BeginInvoke(sender, TestCallback, "Callback Param"); Title = "ffffffffffff"; int resultstr = mydelegate.EndInvoke(result); Button b = sender as Button; b.Content = resultstr.ToString(); } private int ModifyBtnContent(object b) {//在這個方法以及這個方法中調用的其他方法內都不能操作與界面UI相關的屬性 int i = 0; //Button bt = b as Button; while (i < 10) { //bt.Content = i.ToString(); i++; TestTask(500); } return i; } private void TestCallback(IAsyncResult cont) {//在這個方法以及這個方法中調用的其他方法內都不能操作與界面UI相關的屬性,這可能是因為委托不是this.Dispatcher.BeginInvoke
MessageBox.Show(cont.AsyncState.ToString()); }
cmdThree2是按鈕的點擊事件,在這個方法中實例化了一個委托mydelegate ,並且將方法ModifyBtnContent委托給這個委托實例來執行。可以看到ModifyBtnContent方法的返回值與參數都與委托Parmart的一致。mydelegate.BeginInvoke的第一個參數就是ModifyBtnContent的參數,第二個參數是回調函數,再后面的參數是回調函數的參數。IAsyncResult result 這個result 是委托執行完方法ModifyBtnContent后的返回值,類型必須是IAsyncResult。從點擊按鈕后的程序的執行情況來看,Title = "ffffffffffff";這一句是與委托中的方法同時執行的,在int resultstr = mydelegate.EndInvoke(result);這一句之前IAsyncResult result之后的所有語句都是與委托中的方法同時執行的。當程序執行到int resultstr = mydelegate.EndInvoke(result);時,程序會掛起,等待委托中的方法執行完,這一條語句之后的查詢將和回調函數同時執行。