• 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);时,程序会挂起,等待委托中的方法执行完,这一条语句之后的查询将和回调函数同时执行。
  • 相关阅读:
    composer使用git作为仓储
    monolog记录日志
    lumen laravel response对象返回数据
    lumen中间件 Middleware
    AcWing 901. 滑雪
    leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
    acwing 902. 最短编辑距离
    ACWING 844. 走迷宫
    leetcode 5199. 交换字符串中的元素
    AcWing 836. 合并集合
  • 原文地址:https://www.cnblogs.com/jin-/p/5021632.html
Copyright © 2020-2023  润新知