WPF知识点全攻略15- 线程处理

发布时间 2023-05-09 13:42:49作者: 狂想NICE

使用WPF构建应用程序时,想要保证系统的流畅性、用户的体验性,处理好UI线程(主线程)与其他线程(子线程)的关系是必要的。

以最近大火的直播带货为例,镜头前主播(部分副播)的语言动作是主线程,镜头外的场控、客服等人员,各自都有一个属于自己的子线程。场控在做软硬件调试、商品上架下架、发优惠信息,临时更改产品价格、数据监测,客服处理已经出单,物流,复购,销售统计等等操作,这些不可能都交给主播来做,不然时不时的冷场就太尴尬了。要让场控、客服等在镜头外做好辅助工作,并适时的把需要展示的信息传递给主播,才能保证直播的质量。

回归代码本身,要处理线程问题,一般使用Dispatcher和BackgroundWorker来处理子线程与主线程的交互。

1、Dispatcher使用

 新建一个简单的WPF项目、只放置一个Button和一个TextBlock,点击Button,使用子线程修改TextBlock的值

    <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
        <Button
            Margin="10,0"
            Click="Button_Click"
            Content="变更" />
        <TextBlock
            Name="txt"
            VerticalAlignment="Center"
            Text="1" />
    </StackPanel>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(UpdateText);
            thread.Start();
        }

        private void UpdateText()
        {
            Thread.Sleep(TimeSpan.FromSeconds(1));

            //1、直接修改,报错:System.InvalidOperationException:“调用线程无法访问此对象,因为另一个线程拥有该对象。”
            //this.txt.Text = "New text";

            //2、使用Dispatcher,把子线程的方法,丢回给主线程执行。
            Dispatcher.BeginInvoke(new Action(() =>
            {
                this.txt.Text = "New text";
            }));
        }

明显 Dispatcher的使用,把子线程中需要操作主线程的部分,丢回给了主线程,这样完美结果了更改报错的问题。但这种写法不适合在其方法内部处理耗时任务,同时过多的使用Thread来处理异步方法,也会带来不易管理、处理共享数据加锁等问题。

处理简单逻辑是可以使用Dispatcher,处理较为耗时、可能牵扯共享数据或者为方便的管理子线程的启动、停止等,微软提供了BackgroundWorker组件,来安全的处理这些问题。

2、BackgroundWorker的使用

 下面代码实现了一个查找指定范围整数范围内的所有素数的功能,XAML代码如下:

        <Grid Margin="5">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>

            <TextBlock Margin="5">起始:</TextBlock>
            <TextBox
                Name="txtFrom"
                Grid.Column="1"
                Margin="5">
                1
            </TextBox>
            <TextBlock Grid.Row="1" Margin="5">结束:</TextBlock>
            <TextBox
                Name="txtTo"
                Grid.Row="1"
                Grid.Column="1"
                Margin="5">
                500000
            </TextBox>

            <StackPanel
                Grid.Row="2"
                Grid.Column="1"
                Orientation="Horizontal">
                <Button
                    Name="cmdFind"
                    Margin="5"
                    Padding="3"
                    Click="cmdFind_Click">
                    查找素数
                </Button>
                <Button
                    Name="cmdCancel"
                    Margin="5"
                    Padding="3"
                    Click="cmdCancel_Click"
                    IsEnabled="False">
                    停止
                </Button>
            </StackPanel>

            <TextBlock Grid.Row="3" Margin="5">结果:</TextBlock>
            <ListBox
                Name="lstPrimes"
                Grid.Row="3"
                Grid.Column="1"
                Margin="5" />

            <TextBlock Grid.Row="4" Margin="5">进度:</TextBlock>
            <ProgressBar
                Name="progressBar"
                Grid.Row="4"
                Grid.Column="1"
                Height="20"
                MinHeight="20"
                Margin="5"
                VerticalAlignment="Bottom"
                Maximum="100"
                Minimum="0" />
        </Grid>

.cs中的C#代码如下:

        public MainWindow()
        {
            InitializeComponent();
           
            backgroundWorker = new BackgroundWorker();
            backgroundWorker.WorkerReportsProgress = true;
            backgroundWorker.WorkerSupportsCancellation = true;

            backgroundWorker.DoWork += backgroundWorker_DoWork;
            backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
            backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
        }


        private BackgroundWorker backgroundWorker;

        private void cmdFind_Click(object sender, RoutedEventArgs e)
        {
            // 控制按钮、清空数据
            cmdFind.IsEnabled = false;
            cmdCancel.IsEnabled = true;
            lstPrimes.Items.Clear();

            // 获取整数方位
            int from, to;
            if (!Int32.TryParse(txtFrom.Text, out from))
            {
                MessageBox.Show("Invalid From value.");
                return;
            }
            if (!Int32.TryParse(txtTo.Text, out to))
            {
                MessageBox.Show("Invalid To value.");
                return;
            }

            // 使用RunWorkerAsync,在子线程中拾取所有素数
            FindPrimesInput input = new FindPrimesInput(from, to);
            backgroundWorker.RunWorkerAsync(input);
        }

        /// <summary>
        /// BackgroundWorker执行方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            // 获取传入参数
            FindPrimesInput input = (FindPrimesInput)e.Argument;

            // 开始素数拾取
            int[] primes = Worker.FindPrimes(input.From, input.To, backgroundWorker);

            if (backgroundWorker.CancellationPending)
            {
                e.Cancel = true;
                return;
            } 
            // 返回结果
            e.Result = primes;
        }

        /// <summary>
        /// BackgroundWorker执行完成
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                MessageBox.Show("Search cancelled.");
            }
            else if (e.Error != null)
            {
                // An error was thrown by the DoWork event handler.
                MessageBox.Show(e.Error.Message, "An Error Occurred");
            }
            else
            {
                int[] primes = (int[])e.Result; //结果放入ListBox
                foreach (int prime in primes)
                {
                    lstPrimes.Items.Add(prime);
                }
            }

            //重置按钮、进度
            cmdFind.IsEnabled = true;
            cmdCancel.IsEnabled = false;
            progressBar.Value = 0;
        }

        /// <summary>
        /// BackgroundWorker执行进度
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;//变更进度显示
        }

        /// <summary>
        /// 取消
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmdCancel_Click(object sender, RoutedEventArgs e)
        {
            backgroundWorker.CancelAsync();
        }

         

Worker.cs:

    public class FindPrimesInput
    {
        public int To
        { get; set; }

        public int From
        { get; set; }

        public FindPrimesInput(int from, int to)
        {
            To = to;
            From = from;
        }

    }

    public class Worker
    {
        public static int[] FindPrimes(int fromNumber, int toNumber)
        {
            return FindPrimes(fromNumber, toNumber, null);
        }

        public static int[] FindPrimes(int fromNumber, int toNumber, System.ComponentModel.BackgroundWorker backgroundWorker)
        {
            int[] list = new int[toNumber - fromNumber];

            // Create an array containing all integers between the two specified numbers.
            for (int i = 0; i < list.Length; i++)
            {
                list[i] = fromNumber;
                fromNumber += 1;
            }


            //find out the module for each item in list, divided by each d, where
            //d is < or == to sqrt(to)
            //if the remainder is 0, the nubmer is a composite, and thus
            //we mark its position with 0 in the marks array,
            //otherwise the number is a prime, and thus mark it with 1
            int maxDiv = (int)Math.Floor(Math.Sqrt(toNumber));

            int[] mark = new int[list.Length];


            for (int i = 0; i < list.Length; i++)
            {
                for (int j = 2; j <= maxDiv; j++)
                {

                    if ((list[i] != j) && (list[i] % j == 0))
                    {
                        mark[i] = 1;
                    }

                }

                
                int iteration = list.Length / 100;
                if ((i % iteration == 0) && (backgroundWorker != null))
                {                
                    if (backgroundWorker.CancellationPending)
                    {
                        // Return without doing any more work.
                        return null;                      
                    }

                    if (backgroundWorker.WorkerReportsProgress)
                    {
                        //float progress = ((float)(i + 1)) / list.Length * 100;
                        backgroundWorker.ReportProgress(i / iteration);
                        //(int)Math.Round(progress));
                    }
                }

            }

            //create new array that contains only the primes, and return that array
            int primes = 0;
            for (int i = 0; i < mark.Length; i++)
            {
                if (mark[i] == 0) primes += 1;

            }

            int[] ret = new int[primes];
            int curs = 0;
            for (int i = 0; i < mark.Length; i++)
            {
                if (mark[i] == 0)
                {
                    ret[curs] = list[i];
                    curs += 1;
                }
            }

            if (backgroundWorker != null && backgroundWorker.WorkerReportsProgress)
            {
                backgroundWorker.ReportProgress(100);
            }

            return ret;

        }
         
    }
View Code

执行效果:

 

BackgroundWorker的DoWork事件不可操作主线程(界面对象),用来完成一些其他耗时的后台任务,RunWorkerCompleted事件把执行结果反馈给主线程,ProgressChanged事件把执行进度反馈给主线程。

简单总结一下,Dispatcher是一种简单粗暴的处理多线程问题的方式,适合业务逻辑简单线程少的场景,BackgroundWorker则是一种安全、可控的方式,非常适用于后台文件上传下载、应用更新、实时监控等与界面交互较多场景。对于其他与界面交互不多的场景,则使用.NET的线程支持(async/await等)自主实现。

 

WPF知识点全攻略目录:https://www.cnblogs.com/kuangxiangnice/p/11040070.html