WPF 多线程进度对话框

Posted

技术标签:

【中文标题】WPF 多线程进度对话框【英文标题】:WPF multithreaded progress dialog 【发布时间】:2010-12-25 19:40:50 【问题描述】:

更新这是我遇到的一个有趣的问题。我需要在后台进程运行时显示进度对话框。通常,这会起作用,但问题是我需要在后台进程中设置公共静态数据。这是我试图完成的一个示例:

公共部分类 MainWindow : 窗口 公共静态服务绑定; 公共静态结果 lr; 公共进度对话框 dlg; 私人无效登录() 字符串 sPwd = txtPwd.密码; 字符串 sEmail = txtEmail.Text; 绑定=新服务(); lr = binding.login(sEmail, sPwd); 私人无效btnLogin_Click(对象发送者,RoutedEventArgs e) BackgroundWorker 工作者 = 新的 BackgroundWorker; worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted) worker.RunWorkerAsync(); dlg = 新进度对话框(); dlg.Show(); 登录(); private void worker_DoWork(object sender, DoWorkEventArgs e) e.Result = login(); 私人无效工人_RunWorkerCompleted(对象发送者,RunWorkerCompletedEventArgs e) this.Hid(); 窗口 1 新窗口 = 新窗口 1(); 新窗口.Show(); dlg.关闭();

我知道,就目前而言,它不会起作用,因为 login() 是一个 void 并且实际上并没有返回一个值以在 DoWork 事件中与 e.Result 一起使用。但是,我已经设置了一个登录类来传递参数,但我仍然收到错误消息,指出我无法访问 UI 线程。主要问题是 lr 和 binding 被另一个窗口访问,所以它们必须是公共静态数据(从另一个窗口我设置 public static Service binding = MainWindow.binding;)。我只是在思考如何设置它时遇到了一些麻烦。

【问题讨论】:

【参考方案1】:

你想错了。

您需要在主线程中显示进度对话框,而不是在后台工作线程中。

然后,在 BackgroundWorker.DoWork 处理程序中,您执行实际工作。这在后台线程中运行。

在您的工作进行中时,请定期致电BackgroundWorker.ReportProgress。这将在 UI 线程上推动进度。

您需要订阅BW.ProgressChanged 事件,您可以在此处更新progressDialog 中的进度条。这会在 UI 线程上自动发生。

您的工作以及对 ReportProgress 的调用必须是 DoWork 事件处理程序中的唯一内容。

【讨论】:

对,我更新了我的问题以反映这一点(这是正确的)但是,当我尝试在我的 login() 函数中设置变量时出现错误。【参考方案2】:

您需要从前台线程上的文本框(txtEmail 和 txtPwd)读取,而不是后台线程。这是一个例子:

class MainWindow

  private string _email;
  private string _password;

  private void btnLogin_Click(...)
  
    // running on UI thread here - can touch text boxes
    _email = txtEmail.Text;
    _password = txtPwd.Text;
    // ... set up worker ...
    worker.RunWorkerAsync();
  

  private void login()
  
    binding = new Service();
    // running on background thread here
    // but safe to access _email and _password they're just data, not UI controls
    lr = binding.login(_email, _password);
  

编辑:更好的是,将电子邮件和密码作为参数传递,而不是将它们存储在成员变量中:

  private void btnLogin_Click(...)
  
    // running on UI thread here - can touch text boxes
    LoginInfo loginInfo = new LoginInfo(txtEmail.Text, txtPwd.Text);
    // ... set up worker ...
    worker.RunWorkerAsync(loginInfo);  // note argument
  

  private void worker_DoWork(...)
  
    LoginInfo loginInfo = (LoginInfo)(e.Argument);
    // now pass the LoginInfo to login() as an argument
  

(并删除 _email 和 _password 成员)

【讨论】:

【参考方案3】:

要访问从非 UI 线程中引发的事件调用的事件处理程序中的 UI 元素,您需要类似以下代码:

Action action = () => FinalUpdate();
if (Thread.CurrentThread != Dispatcher.Thread)

    Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, action);

else

    action();

如果可以,请获取 Bill Wagner 的 More Effective C# 副本。他有一整节关于多线程和后台工作线程。

【讨论】:

您可以考虑使用if (Dispatcher.CheckAccess()) action(); else Invoke(action);,而不是您的if 语句。不知道为什么 CheckAccess() 是一个隐藏函数(不会出现在 Intellisense 中),但它非常适合这个。请参阅msdn.microsoft.com/en-us/library/… 以获取与您的几乎相同的示例。 @Will Eddins - 感谢您的提示,现在代码看起来干净多了。

以上是关于WPF 多线程进度对话框的主要内容,如果未能解决你的问题,请参考以下文章

在 MFC 中不断增加进度条

进度对话框和后台线程处于活动状态时如何处理屏幕方向更改?

进度对话框和后台线程处于活动状态时如何处理屏幕方向更改?

Android 中ProgressDialog进度条对话框的使用(使用子线程模拟更新进度)

wpf多线程进度条更新进度

Android 中ProgressDialog进度条对话框的使用(使用子线程模拟更新进度)