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);时,程序会挂起,等待委托中的方法执行完,这一条语句之后的查询将和回调函数同时执行。