• 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);时,程序会挂起,等待委托中的方法执行完,这一条语句之后的查询将和回调函数同时执行。
  • 相关阅读:
    基本MVVM 和 ICommand用法举例(转)
    WPF C# 命令的运行机制
    628. Maximum Product of Three Numbers
    605. Can Place Flowers
    581. Shortest Unsorted Continuous Subarray
    152. Maximum Product Subarray
    216. Combination Sum III
    448. Find All Numbers Disappeared in an Array
    268. Missing Number
    414. Third Maximum Number
  • 原文地址:https://www.cnblogs.com/jin-/p/5021632.html
Copyright © 2020-2023  润新知