WPF 的 BackgroundWorker

来源:互联网 发布:c语言编程界面 编辑:程序博客网 时间:2024/03/28 19:24

最近在做 WPF 显示和控制网络摄像机的任务。一个重要的小问题是窗口显示与后台处理的同步问题。例如,在登录(或切换状态、播放视频等)的时候,如果这么写代码:

private void BtnLogin_Click(object sender, RoutedEventArgs e){    bool isConnected = Login(m_IP, m_Port);    if (isConnected)    {        MessageBox.Show("登录成功!");    }    else    {        MessageBox.Show("登录失败!");    }}

那么在登录状态返回之前,窗口将一直无法响应用户操作(如移动等)。这是因为状态显示与后台处理置于同一个线程中,这显然是不合理的。

所以,更合理的做法应该是将窗口的控制与显示,和后台数据的处理,放在两个不同的线程中异步处理。WPF 的 BackgroundWorker 可以十分简洁地实现这一点。关于 BackgroundWorker,文章 Multi-threading with the BackgroundWorker 介绍得非常好,本文基于它做一些笔录。

简单实例

假设我们要在后台统计 1~10000 之间所有能被 42 整除的数字的个数,并在每次找到一个时,实时地在前端显示出来。

首先在代码开头处引用:

using System.ComponentModel;

并在事件响应函数(如 Button_Click)的主体部分加入:

BackgroundWorker worker = new BackgroundWorker();worker.WorkerReportsProgress = true;worker.DoWork += worker_DoWork;worker.ProgressChanged += worker_ProgressChanged;worker.RunWorkerCompleted += worker_RunWorkerCompleted;worker.RunWorkerAsync(10000);

也就是对 worker 对象添加了三个事件处理函数:DoWork, ProgressChanged 和 RunWorkerCompleted。DoWork 用于后台数据处理的主要过程,并在必要时候汇报进度(ReportProgress);ProgressChanged 用于在 DoWork 汇报进度时决定做什么;RunWorkerCompleted 用于在 DoWork 结束时执行后续的处理或显示等工作。

完整代码

MainWindow.xaml:

<Window x:Class="MTTest.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"        xmlns:local="clr-namespace:MTTest"        mc:Ignorable="d"        Title="MainWindow" Height="350" Width="525">    <DockPanel Margin="10">        <DockPanel DockPanel.Dock="Top">            <Button Name="btnDoSynchronousCalculation" Click="btnDoSynchronousCalculation_Click"                     DockPanel.Dock="Left" HorizontalAlignment="Left">                    Synchronous (same thread)</Button>            <Button Name="btnDoAsynchronousCalculation" Click="btnDoAsynchronousCalculation_Click"                     DockPanel.Dock="Right" HorizontalAlignment="Right">                    Asynchronous (worker thread)</Button>        </DockPanel>        <ProgressBar DockPanel.Dock="Bottom" Height="18" Name="pbCalculationProgress" />        <ListBox Name="lbResults" Margin="0,10" />    </DockPanel></Window>

MainWindow.cs:

using System;using System.Windows;using System.ComponentModel;namespace MTTest{    /// <summary>    /// Interaction logic for MainWindow.xaml    /// </summary>    public partial class MainWindow : Window    {        public MainWindow()        {            InitializeComponent();        }        private void btnDoSynchronousCalculation_Click(object sender, RoutedEventArgs e)        {            int max = 10000;            pbCalculationProgress.Value = 0;            lbResults.Items.Clear();            int result = 0;            for (int i = 0; i < max; i++)            {                if (i % 42 == 0)                {                    lbResults.Items.Add(i);                    result++;                }                System.Threading.Thread.Sleep(1);                pbCalculationProgress.Value = Convert.ToInt32(((double)i / max) * 100);            }            MessageBox.Show("Numbers between 0 and 10000 divisible by 7: " + result);        }        private void btnDoAsynchronousCalculation_Click(object sender, RoutedEventArgs e)        {            pbCalculationProgress.Value = 0;            lbResults.Items.Clear();            BackgroundWorker worker = new BackgroundWorker();            worker.WorkerReportsProgress = true;            worker.DoWork += worker_DoWork; // background computing            worker.ProgressChanged += worker_ProgressChanged; // progress reporting            worker.RunWorkerCompleted += worker_RunWorkerCompleted; // computation completed            worker.RunWorkerAsync(10000);        }        void worker_DoWork(object sender, DoWorkEventArgs e)        {            int max = (int)e.Argument;            int result = 0;            for (int i = 0; i < max; i++)            {                int progressPercentage = Convert.ToInt32(((double)i / max) * 100);                if (i % 42 == 0)                {                    result++;                    (sender as BackgroundWorker).ReportProgress(progressPercentage, i);                }                else                    (sender as BackgroundWorker).ReportProgress(progressPercentage);                System.Threading.Thread.Sleep(1);            }            e.Result = result;        }        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)        {            pbCalculationProgress.Value = e.ProgressPercentage;            if (e.UserState != null)                lbResults.Items.Add(e.UserState);        }        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)        {            MessageBox.Show("Numbers between 0 and 10000 divisible by 7: " + e.Result);        }    }}

实际应用

基于 BackgroundWorker,文章开头的问题就可以通过如下方式解决:

private void BtnLogin_Click(object sender, RoutedEventArgs e){    string param = m_IP + ":" + m_Port;    BackgroundWorker worker = new BackgroundWorker();    worker.DoWork += Login_DoWork;    worker.RunWorkerCompleted += Login_RunWorkerCompleted;    worker.RunWorkerAsync(param);    LabelLoginState.Content = "正在连接...";private void Connect_DoWork(object sender, DoWorkEventArgs e){    string[] param = ((string)e.Argument).Split(':');    string ip = param[0];    string port = param[1];    m_IsConnected = Login(ip, port);}private void Connect_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){    if (m_IsConnected)    {        LblConnectSate.Content = "已连接!";    }    else    {        LblConnectSate.Content = "连接失败!";    }}

完!

参考文献

  1. Multi-threading with the BackgroundWorker.
0 0
原创粉丝点击