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);时,程序会挂起,等待委托中的方法执行完,这一条语句之后的查询将和回调函数同时执行。
原文地址:https://www.cnblogs.com/jin-/p/5021632.html