后台工作程序在WPF中无法正常工作

时间:2012-10-03 10:32:20

标签: c# .net wpf desktop

我想在数据网格中显示临时文件,因此这是一个长期的过程,我在C#.net WPF应用程序中使用后台工作程序。

我的代码是

 private System.ComponentModel.BackgroundWorker _background = new System.ComponentModel.BackgroundWorker();

   private void button1_Click(object sender, RoutedEventArgs e)
        {
          _background.RunWorkerAsync();
        }

    public MainWindow()
       {
           InitializeComponent();
           this._background.DoWork += new DoWorkEventHandler(_background_DoWork);
           this._background.RunWorkerCompleted += new       
           RunWorkerCompletedEventHandler(_background_RunWorkerCompleted);
           this._background.WorkerReportsProgress = true;
           _background.WorkerSupportsCancellation = true;

       }


void _background_DoWork(object sender, DoWorkEventArgs e)

{

this.Dispatcher.Invoke((Action)(() =>
    {
        try
        {
            FileInfo[] files = new   
            DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles();

            foreach (FileInfo fi in files)
            {
                if (fi != null)              
                 {                 
                    dataGrid1.Items.Add(fi);           

                }
            }           
        }
        catch { }
    }));
}

void _background_RunWorkerCompleted(object sen, RunWorkerCompletedEventArgs e)
      {

          if (e.Cancelled)
          {
             MessageBox.Show("Cancelled");
          }
          else if (e.Error != null)
          {
               MessageBox.Show("Exception Thrown");
          }

     }

所有代码都在运行但是在加载datagrid时它会挂起意味着我的UI在程序运行时没有响应。

在上述条件下顺利运行后台工作程序需要进行哪些修改?

除此之外,如果我想添加一个与此应用程序一起进展的ProgressBar,那么我必须做什么?

谢谢

4 个答案:

答案 0 :(得分:3)

通过使用this.Dispatcher.Invoke,您可以有效地将工作编组回UI线程。这没有任何意义:在执行此操作时,您正在阻止UI线程。

将工作分为两部分:

  • 缓慢的部分,即检索文件,应该在Dispatcher.Invoke
  • 之外完成
  • UI更新,必须在Dispatcher.Invoke中完成,或者(更好)在RunWorkerCompleted事件处理程序中完成。

后台工作程序组件完全正确,因此您无需使用调度程序手动分派UI工作。例如,您可以将文件存储在填写DoWork方法的字段中,并用于填充RunWorkerCompleted事件中的数据网格:

FileInfo[] files;

void _background_DoWork(object sender, DoWorkEventArgs e)
{
    files = new DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles();
}

void _background_RunWorkerCompleted(object sen, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        MessageBox.Show("Cancelled");
    }
    else if (e.Error != null)
    {
        MessageBox.Show("Exception Thrown");
    }
    else 
    { 
         foreach (FileInfo fi in files)
         {
              dataGrid1.Items.Add(fi);           
         }
    }
}

注意:如果您使用的是C#5,那么现在使用async/await功能可以更轻松地使用它。所有你需要的是这样的:

    private async void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        try
        {
            var files = await Task.Run(() => 
                new DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles()
            );
            foreach (var f in files)
                this.dataGrid1.Items.Add(f.Name);
        }
        catch (Exception e)
        {
            MessageBox.Show("Exception thrown!"); // do proper error handling here...
        }
        finally
        {
            button1.Enabled = true;
        }
    }

编译器负责所有剩下的事情。

答案 1 :(得分:0)

使用DirectoryInfo.EnumerateFiles

这一行:

this.Dispatcher.Invoke

在主线程上同步执行代码,因此使用BackgroudWorker这样的方式没有任何好处,因为DirectoryInfo.GetFiles仅在枚举目录中的所有文件时返回。

另一方面,DirectoryInfo.EnumerateFiles是懒惰的。你可以这样写:

void _background_DoWork(object sender, DoWorkEventArgs e)
{
    var info = new DirectoryInfo(System.IO.Path.GetTempPath());

    // now enumeration happens in background
    foreach (var fi in info.EnumerateFiles())
    {
        // main thread in used only when there is next enumeration result available
        Dispatcher.Invoke((Action)(() => dataGrid1.Items.Add(fi)));
    }
}

答案 2 :(得分:0)

尝试从Dispatcher中取消此操作:

FileInfo[] files = new DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles();

它应该只执行涉及UI访问或修改的小而快的操作。请看这个链接:http://msdn.microsoft.com/en-us/magazine/cc163328.aspx

您可以通过BackgroundWorker完成繁重的工作,并使用Dispatcher更新dataGrid.Items集合。

尝试使用Dispatcher:

Dispatcher.BeginInvoke()

答案 3 :(得分:0)

您应该在RunWorkerComplete或ProgressChanged事件处理程序中更新UI。

尝试这样的事情:

    public Program()
    {
        w = new BackgroundWorker();
        w.DoWork += new DoWorkEventHandler(w_DoWork);
        w.ProgressChanged += new ProgressChangedEventHandler(w_ProgressChanged);
        w.RunWorkerCompleted += new RunWorkerCompletedEventHandler(w_RunWorkerCompleted);
        w.WorkerReportsProgress = true;
        w.WorkerSupportsCancellation = true;

        w.RunWorkerAsync();
    }

    void w_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        FileInfo[] files = e.Result as FileInfo[];
        foreach (FileInfo fi in files)
        {
            //dataGrid1.Items.Add(fi);  
        }
    }

    void w_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        FileInfo fi = e.UserState as FileInfo;
        //dataGrid1.Items.Add(fi);  
    }

    void w_DoWork(object sender, DoWorkEventArgs e)
    {
        var w = sender as BackgroundWorker;

        FileInfo[] files = new DirectoryInfo(
            Path.GetTempPath()).GetFiles();

        // Using ProgressChanged
        foreach (FileInfo fi in files)
        {
            w.ReportProgress(0, fi);
        }

        // Using RunWorkerCompleted
        e.Result = files;
    }

此外,在dowork中不需要try / catch,在runworkercomplete事件中会自动捕获异常并将其报告为错误。