WPF QuickStart系列之线程模型(Thread Model)

简介: 原文:WPF QuickStart系列之线程模型(Thread Model)这篇博客将介绍WPF中的线程模型。 首先我们先来看一个例子,用来计算一定范围内的素数个数。 XAML: ...
原文: WPF QuickStart系列之线程模型(Thread Model)

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

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

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。

我们还可以使用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 > 0 ? "Total Primes: " + total.ToString() : "Canceled.";

                    button.IsEnabled = true;

                }, null);
                
            });

        }

        private int CountPrimes(int from, int to, CancellationToken token)
        {
            int total = 0;
            for (int i = from; i <= to; i++)
            {
                if(token.IsCancellationRequested)
                {
                    return -1;
                }

                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;
        }

        private void CancelButtonClick(object sender, RoutedEventArgs e)
        {
            if(source != null)
            {
                source.Cancel();
            }
        }

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

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

目录
相关文章
|
3月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
59 7
|
3月前
|
编解码 网络协议 API
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
|
24天前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
26天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
26天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
33 2
|
26天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
26天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
16 1
|
26天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
2月前
|
消息中间件 存储 NoSQL
剖析 Redis List 消息队列的三种消费线程模型
Redis 列表(List)是一种简单的字符串列表,它的底层实现是一个双向链表。 生产环境,很多公司都将 Redis 列表应用于轻量级消息队列 。这篇文章,我们聊聊如何使用 List 命令实现消息队列的功能以及剖析消费者线程模型 。
98 20
剖析 Redis List 消息队列的三种消费线程模型
|
1月前
|
NoSQL Redis 数据库
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
本文解释了Redis为什么采用单线程模型,以及为什么Redis单线程模型的效率和速度依然可以非常高,主要原因包括Redis操作主要访问内存、核心操作简单、单线程避免了线程竞争开销,以及使用了IO多路复用机制epoll。
47 0
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快

热门文章

最新文章