这篇博客将介绍WPF中的线程模型。

首先我们先来看一个例子,用来计算一定范围内的素数个数。

XAML:

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="">
<TextBlock Text="From:" />
<TextBox Margin="10,2,2,2" Width="" MaxLength="" x:Name="_from"/>
<TextBlock Text="To:" Margin="20,0,0,0"/>
<TextBox Margin="10,2,2,2" Width="" MaxLength="" x:Name="_to"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="" Margin="">
<Button Content="Calculate" Padding="" Click="CalcButtonClick" x:Name="_calcButton"/>
<Button Content="Cancel" Padding="" Margin="10,0,0,0" IsEnabled="False" x:Name="_cancelButton" Click="CancleButtonClick"/>
</StackPanel>
<TextBlock x:Name="_result" Grid.Row="" FontSize="" Margin="" 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 = ; for (int i = from; i <= to; i++)
{
bool isPrime = true;
int limit = (int)Math.Sqrt(i);
for (int j = ; j <= limit; j++)
if (i % j == )
{
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。

我们还可以使用SynchronizationContext来进行UI同步,代码如下:

private void CalcButtonClick(object sender, RoutedEventArgs e)
{
_result.Text = string.Empty; int from = int.Parse(_from.Text); int to = int.Parse(_to.Text); Button button = sender as Button; button.IsEnabled = false; SynchronizationContext sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem((p) => { int total = CountPrimes(from, to); sc.Send(delegate { _result.Text = "Total Primes: " + total.ToString(); button.IsEnabled = true; }, null);
});
}

SynchronizationContext中有两个方法用来做UI同步,SendPost,这两者区别于Dispatcher中BeginInvoke和Invoke是一样的。Send方法是异步进行UI同步,Post方法是同步的方式进行UI同步。

说完了WPF中UI线程同步,下面我们看一下如何取消正在进行中的线程操作。

使用CancellationTokenSource类,代码如下:

CancellationTokenSource source = null;

        public MainWindow()
{
InitializeComponent();
} private void CalcButtonClick(object sender, RoutedEventArgs e)
{
source = new CancellationTokenSource(); _result.Text = string.Empty; int from = int.Parse(_from.Text); int to = int.Parse(_to.Text); Button button = sender as Button; button.IsEnabled = false; SynchronizationContext sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem((p) => { int total = CountPrimes(from, to,source.Token); sc.Send(delegate { _result.Text = total > ? "Total Primes: " + total.ToString() : "Canceled."; button.IsEnabled = true; }, null); }); } private int CountPrimes(int from, int to, CancellationToken token)
{
int total = ;
for (int i = from; i <= to; i++)
{
if(token.IsCancellationRequested)
{
return -;
} bool isPrime = true;
int limit = (int)Math.Sqrt(i);
for (int j = ; j <= limit; j++)
if (i % j == )
{
isPrime = false;
break;
}
if (isPrime)
total++;
}
return total;
} private void CancelButtonClick(object sender, RoutedEventArgs e)
{
if(source != null)
{
source.Cancel();
}
}

至此,我们将WPF中后台线程与UI的同步写完了。还有一个重要部分,就是将执行的进度在客户端展示,这样可以让用户清楚的知道目前执行的进度,而不是一直等待,增强了用户体验。可以使用BackgroudWorker来实现。在此就不再赘述。

感谢您的阅读,如果您对博客中内容有疑问,欢迎在评论中指出来。

WPF QuickStart系列之线程模型(Thread Model)的更多相关文章

  1. WPF QuickStart系列

    接触WPF有一段时间了,现在做的项目也是WPF相关的.所以决定写一个WPF QuickStart系列的文章.也是自己对WPF学习的总结,如果对你有帮助,就非常棒了.因为不善言辞,所以尽量以WPF示例和 ...

  2. 编写高质量代码改善C#程序的157个建议——建议87:区分WPF和WinForm的线程模型

    建议87:区分WPF和WinForm的线程模型 WPF和WinForm窗体应用程序都有一个要求,那就是UI元素(如Button.TextBox等)必须由创建它的那个线程进行更新.WinForm在这方面 ...

  3. 死磕 java线程系列之线程模型

    问题 (1)线程类型有哪些? (2)线程模型有哪些? (3)各语言使用的是哪种线程模型? 简介 在Java中,我们平时所说的并发编程.多线程.共享资源等概念都是与线程相关的,这里所说的线程实际上应该叫 ...

  4. 编写高质量代码改善程序的157个建议:第87个建议之区分WPF和WinForm的线程模型

    今天有时间了,继续<编写高质量代码改善程序的157个建议>的阅读,当我阅读到建议87的时候,里面的一些代码示例和文中所说的不一致了,是不是我现在用的是NetFramework 4.0的缘故 ...

  5. WPF QuickStart系列之样式和模板(Style and Template)

    在WPF桌面程序中,当我们想构建一个统一的UI表现时(在不同操作系统下,显示效果一致),此时我们就需要使用到WPF中的样式和模板技术.简单来说,如果我们需要简单的给一个Button设置宽,高,Marg ...

  6. WPF QuickStart系列之数据绑定(Data Binding)

    这篇博客将展示WPF DataBinding的内容. 首先看一下WPF Data Binding的概览, Binding Source可以是任意的CLR对象,或者XML文件等,Binding Targ ...

  7. WPF QuickStart系列之附加属性(Attached Property)

    这一篇博客是关于如何使用附加属性和创建自定义附加属性的. 1. 附加属性使用, WPF中对附加属性使用最多的莫过于对控件布局时设置控件的位置,例如在Canvas中有一个Rectangle, Ellip ...

  8. 「译」JUnit 5 系列:扩展模型(Extension Model)

    原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...

  9. posix 线程(一):线程模型、pthread 系列函数 和 简单多线程服务器端程序

    posix 线程(一):线程模型.pthread 系列函数 和 简单多线程服务器端程序 一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属 ...

随机推荐

  1. 使用socket方式连接Nginx优化php-fpm性能

    Nginx连接fastcgi的方式有2种:TCP和unix domain socket 什么是Unix domain socket?-- 维基百科 Unix domain socket 或者 IPC ...

  2. linux系统vsftpd登陆慢卡怎么办

    linux系统vsftpd登陆慢卡怎么办 浏览:145 | 更新:2013-12-31 00:50 vsftpd是linux系统中的一款ftp软件,用它可以实现文件,数据上传与下载,但有些用户会发现v ...

  3. STL---vector(向量)

    1 基本操作 (1)头文件#include<vector>. (2)创建vector对象,vector<int> vec; (3)尾部插入数字:vec.push_back(a) ...

  4. 时间和日期控件(Calendar1)

    取得选择的: taskItem["data"] = Calendar1.SelectedDate.ToShortDateString();

  5. POJ 2676

    http://poj.org/problem?id=2676 深搜的题目. 题意呢就是一个数独的游戏,应该都知道规则. 思路:我的思路很简单,就是用数组来判断某个数字是否可以使用,而每一个数字都由三个 ...

  6. ios中的RunLoop 和 android 中的Looper

    今天写android程序,用到了Handler,晚上回来查阅资料,发现了Looper这个概念. 看了一下网上关于Looper的资料,发现这个Looper跟ios中的Runloop基本的理念完全一致! ...

  7. MongoDB 副本集的原理、搭建、应用

    概念: 在了解了这篇文章之后,可以进行该篇文章的说明和测试.MongoDB 副本集(Replica Set)是有自动故障恢复功能的主从集群,有一个Primary节点和一个或多个Secondary节点组 ...

  8. [转] This Android SDK requires Android Developer Toolkit version 23.0.0 or above

    问题描述: This Android SDK requires Android Developer Toolkit version 23.0.0 or above.  Current version ...

  9. POJ 1681 Painter's Problem (高斯消元)

    题目链接 题意:有一面墙每个格子有黄白两种颜色,刷墙每次刷一格会将上下左右中五个格子变色,求最少的刷方法使得所有的格子都变成yellow. 题解:通过打表我们可以得知4*4的一共有4个自由变元,那么我 ...

  10. 51nod 1117 聪明的木匠 (哈夫曼树)

    题目:传送门. 题意:中文题. 题解:就是构造一颗哈夫曼树,数据结构里的知识. #include <iostream> #include <cstdio> #include & ...