首先我们先来看一个例子,用来计算一定范围内的素数个数。
XAML:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Margin="6"> <TextBlock Text="From:" /> <TextBox Margin="10,2,2,2" Width="120" MaxLength="10" x:Name="_from"/> <TextBlock Text="To:" Margin="20,0,0,0"/> <TextBox Margin="10,2,2,2" Width="120" MaxLength="10" x:Name="_to"/> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="6"> <Button Content="Calculate" Padding="4" Click="CalcButtonClick" x:Name="_calcButton"/> <Button Content="Cancel" Padding="4" Margin="10,0,0,0" IsEnabled="False" x:Name="_cancelButton" Click="CancleButtonClick"/> </StackPanel> <TextBlock x:Name="_result" Grid.Row="3" FontSize="20" Margin="6" HorizontalAlignment="Center" /> </Grid>
C#:
private void CalcButtonClick(object sender, RoutedEventArgs e) { _result.Text = string.Empty; int from = int.Parse(_from.Text); int to = int.Parse(_to.Text); _calcButton.IsEnabled = false; _cancelButton.IsEnabled = true; int total = CountPrimes(from, to); _result.Text = "Total Primes: " + total.ToString(); _calcButton.IsEnabled = true; } private int CountPrimes(int from, int to) { int total = 0; for (int i = from; i <= to; i++) { bool isPrime = true; int limit = (int)Math.Sqrt(i); for (int j = 2; j <= limit; j++) if (i % j == 0) { isPrime = false; break; } if (isPrime) total++; } return total; }
运行之后,如果输入的值很大,例如需要查找1到100000000之间的素数。发现一会儿界面就会卡死。这是因为我们的计算方法阻塞了UI线程。此时我们应该考虑将计算方法放在一个线程中去计算。将计算的代码放置在ThreadPool中,不建议使用Thread,因为ThreadPool会帮助我们来管理线程。
修改CalcButtonClick的C#代码如下:
private void CalcButtonClick(object sender, RoutedEventArgs e) { _result.Text = string.Empty; int from = int.Parse(_from.Text); int to = int.Parse(_to.Text); _calcButton.IsEnabled = false; _cancelButton.IsEnabled = true; ThreadPool.QueueUserWorkItem((state) => { int total = CountPrimes(from, to); _result.Text = "Total Primes: " + total.ToString(); _calcButton.IsEnabled = true; }); }
此时查找1到100000000之间的素数,发现界面可以自由的拖拽。不过会产生一个异常。截图如下:
错误信息告诉我们在一个线程中访问另一个线程创建的对象。这是因为_result这个TextBlock是UI线程创建的。我们不能在一个后台线程中访问它。下面就要引出WPF中专门用于UI同步的Dispatcher对象。继续改进代码,
private void CalcButtonClick(object sender, RoutedEventArgs e) { _result.Text = string.Empty; int from = int.Parse(_from.Text); int to = int.Parse(_to.Text); _calcButton.IsEnabled = false; _cancelButton.IsEnabled = true; ThreadPool.QueueUserWorkItem((state) => { int total = CountPrimes(from, to); Dispatcher.BeginInvoke(new Action(() => { _result.Text = "Total Primes: " + total.ToString(); _calcButton.IsEnabled = true; })); }); }
把更新UI的代码都放置在Dispatcher.BeginInvoke中执行。这次我们会看到最终的执行结果。
细心的你会返现Dispatcher对象还有一个Invoke的方法。这两者的区别BeginInvoke是异步更新UI,不会阻塞UI,Invoke方法是同步更新UI,如果需要更新到UI的内容很多,会造成UI的阻塞,建议使用BeginInvoke。
这一篇博客先写这么多,内容太多怕影响阅读效果。后面的博客中会介绍WPF中另外同步UI的方法及取消线程中的操作。
如果您觉得博客中有错误,欢迎在评论中指出。感谢您的阅读。
时间: 2024-10-09 18:14:10