这篇博客将介绍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. treeiso

    主要包括了一些树同构的代码和一些树图生成器... download

  2. 【leetcode】Populating Next Right Pointers in Each Node II

    Populating Next Right Pointers in Each Node II Follow up for problem "Populating Next Right Poi ...

  3. POJ 1917

    http://poj.org/problem?id=1917 poj的字符串的一道水题. 题意么无关紧要, 反正输出的第一行就是把那个<>去掉,s1<s2>s3<s4&g ...

  4. TcxComboBox控件说明

    http://www.cnblogs.com/huangygdelphi/articles/2648490.html 属性: Text:ComboBox 的文本信息 EditText: 也是给Comb ...

  5. ffmpeg-20160526-git-bin

    ESC 退出 0 进度条开关 1 屏幕原始大小 2 屏幕1/2大小 3 屏幕1/3大小 4 屏幕1/4大小 S 下一帧 [ -2秒 ] +2秒 ; -1秒 ' +1秒 下一个帧 -> -5秒 f ...

  6. [转] Android Volley完全解析(一),初识Volley的基本用法

    版权声明:本文出自郭霖的博客,转载必须注明出处.   目录(?)[-] Volley简介 下载Volley StringRequest的用法 JsonRequest的用法   转载请注明出处:http ...

  7. MST:Agri-Net(POJ 1258)

     Agri-Net 题目大意:农夫有一片农场,现在他要把这些田地用管子连起来,田地之间有一定距离,铺设每一段管子的长度与这些田地与田地距离是一样的,问你最小的铺设方案. 这一题很裸,Kruskal算法 ...

  8. codeforces 558B. Amr and The Large Array 解题报告

    题目链接:http://codeforces.com/problemset/problem/558/B 题目意思:给出一个序列,然后找出出现次数最多,但区间占用长度最短的区间左右值. 由于是边读入边比 ...

  9. JQuery 鼠标事件简介

    mouseover事件于用户把鼠标从一个元素移动到另外一个元素上时触发,mouseout事件于用户把鼠标移出一个元素时触发. 下面为你详细介绍下jquery中的鼠标事件: (1):click事件:cl ...

  10. JAVA导出pdf实例

    一.直接导出成PDF   Java代码 1. import java.io.FileNotFoundException; 2. import java.io.FileOutputStream; 3.  ...