使用 async/await 的情况:
private async void Button_Click(object sender, RoutedEventArgs e) { (sender as Button).IsEnabled = false; const string do7zCmd = @"a D:x7zaTest.7z" + @" E:WpfApp1WpfApp1inDebug*.dll" + @" E:WpfApp1WpfApp1inDebug*.xml" ; tex1.Text = "Running..."; tex1.Focus(); var ret = await Task.Run(() => X7za.Do7z(do7zCmd)); //更新UI线程的操作 tex1.Text += " " + ret + " " + do7zCmd; (sender as Button).IsEnabled = true; }
在不使用 async/await 的情况下有 3 种办法:
private void Button_Click(object sender, RoutedEventArgs e) { (sender as Button).IsEnabled = false; const string do7zCmd = @"a D:x7zaTest.7z" + @" E:WpfApp1WpfApp1inDebug*.dll" + @" E:WpfApp1WpfApp1inDebug*.xml" ; //更新UI线程的操作 var f = new Action<int>(ret => { tex1.Text += " " + ret + " " + do7zCmd; (sender as Button).IsEnabled = true; }); //1.BackgroundWorker - 推荐,有进度报告机制,对 UI 的操作在创建线程内 var b = new BackgroundWorker(); b.DoWork += (o, args) => args.Result = X7za.Do7z(do7zCmd); b.RunWorkerCompleted += (o, args) => f((int)args.Result); b.RunWorkerAsync(); //2.委托异步执行 - 主要就是下面的 BeginInvoke 调用, //这是从 BackgroundWorker 的源码里挖出来的用法。 // //这个片段对 UI 线程的操作是在别的线程通过 WPF //Application 对象的 Dispatcher 机制跨线程执行的。 new Action(() => { var ret = X7za.Do7z(do7zCmd); Application.Current.Dispatcher.Invoke(() => f(ret)); }).BeginInvoke(null, null); //3.Task.Run - 很通俗的方法了。对 UI 的操作跟片段2一样。 Task.Run(() => { var ret = X7za.Do7z(do7zCmd); Application.Current.Dispatcher.Invoke(() => f(ret)); }); tex1.Text = "Running..."; tex1.Focus(); }
就是酱紫。
推荐第一种,WinForm 程序也可以用。
【WPF异步更新UI的两种方法】
首先强调一点:异步更新UI实际上就是新开一个线程,执行耗时的任务,但是UI上的东西又不能被其他线程访问,所以非UI线程分析UI的那几条代码用一种特殊的方法来执行,从而实现既可以将耗时的操作放在其他线程,有可以更新UI。
1、方法一
案例:UI上有三个TextBlock,一个Button,当点击button的时候新三个task产生随机数(模拟耗时操作),然后将产生的随机数给UI显示
private void Button_Click(object sender, RoutedEventArgs e) { //也可以Task task = new Task(SchedulerWork);task .Start() Task.Factory.StartNew(SchedulerWork); } private void SchedulerWork() { //fistr,second,three是三个TextBlock控件的名字 Task task = new Task((tb) => Begin(this.first), this.first); Task task2 = new Task((tb) => Begin(this.second), this.first); Task task3 = new Task((tb) => Begin(this.Three), this.first); task.Start(); task2.Start(); task3.Start(); Task.WaitAll(task, task2, task3); } private void Begin(TextBlock tb) { int i = 100000000; while (i > 0) { i--; } Random random = new Random(); String Num = random.Next(0, 100).ToString(); this.Dispatcher.BeginInvoke(new Action(() => tb.Text = Num)); }
主要注意的几点:
1、Begin方法不是在UI线程上执行的,所以他里面不能访问UI上的元素,也就是不能执行tb.Text = Num,否则他就是会报异常:(A first chance exception of type ‘System.InvalidOperationException’ occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object because a different thread owns it.)
2、因此我们更新UI的部分采用:
this.Dispatcher.BeginInvoke(new Action(() => tb.Text = Num));
方法来进行更新,这样实际上就交给UI线程来做了,也就不会报错了。
2 方法二
直接上代码
private void Button_Click(object sender, RoutedEventArgs e) { Task.Factory.StartNew(SchedulerWork); } private readonly TaskScheduler _syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); private void SchedulerWork() { Task task = new Task((tb) => Begin(this.first), this.first); Task task2 = new Task((tb) => Begin(this.second), this.first); Task task3 = new Task((tb) => Begin(this.Three), this.first); task.Start(); task2.Start(); task3.Start(); Task.WaitAll(task, task2, task3); } private void Begin(TextBlock tb) { int i = 100000000; while (i > 0) { i--; } Random random = new Random(); String Num = random.Next(0, 100).ToString(); Task.Factory.StartNew(() => tb.Text = Num, new CancellationTokenSource().Token, TaskCreationOptions.None, _syncContextTaskScheduler).Wait(); }