C#異步操作


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);時,程序會掛起,等待委托中的方法執行完,這一條語句之后的查詢將和回調函數同時執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM