zoukankan      html  css  js  c++  java
  • 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来实现。在此就不再赘述。

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

  • 相关阅读:
    微信开发:微信js_sdk分享,使用场景,网页在微信app内部分享时的标题与描述,包括logo设置(一)
    云服务最开始的初始密码与远程连接密码?
    阿里云域名的ssl证书申请与腾讯服务器域名的证书安装
    关于 https的SNI问题
    关于网页授权access_token和普通access_token的区别
    转载:敏捷开发框架的优势
    select自定义下拉三角符号,css样式小细节
    关于ffmpeg /iis 8.5 服务器下,视频截取第一帧参数配置
    C# WebAPI中使用Swagger
    面向对象编程思想(OOP)
  • 原文地址:https://www.cnblogs.com/yang-fei/p/4608182.html
Copyright © 2011-2022 走看看