1

I am making and app using C# and Winforms that archives and saves folders to specified locations,for archiving folders i have a BackgroundWorker which takes as input a folder path and generates a zip archive.Now in the next step the file needs to be moved at specified location,again as the file is large enough and could hang up UI thread i moved the code to another BackgroundWorker named FileMove,everything works well except that the FileMove is not reporting any progress,here is the function that i call as soon as archiving is over;

  private void FileMove_DoWork(object sender, DoWorkEventArgs e)
    {
        label3.Text = "Saving file,please wait...";
        File.Move(temppath + @"\Output.jpg", savefilename);
    }

    private void FileMove_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        label3.Text = "Saving file,please wait... " + e.ProgressPercentage.ToString(); //This should show Progress Percentage but it doesn't.
    }

    private void FileMove_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        label3.Text = ("The folder has been successfully hidden.");
        button1.Enabled = true;
        button2.Enabled = true;
        button3.Enabled = true;
        this.ControlBox = true;
    }

The problem i'm facing is as soon as file moving starts label3 shows "Saving file,please wait..." and after a long time(as i'm compressing 900-1000 MB)it shows "The folder has been successfully hidden.".During ProgressChanged event label should also show Percentage but it doesn't.Please point out or correct where i've gone wrong.Any help will be appreciated.

Patrick
  • 17,669
  • 6
  • 70
  • 85
devavx
  • 1,035
  • 9
  • 22
  • have you enabled your bw worker to report progress? - [this](http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.workerreportsprogress.aspx) – Sayse Aug 28 '13 at 21:16
  • Yes Sayse,the BackgroundWorker is enabled to report progress. – devavx Aug 28 '13 at 21:17
  • Then its probably because your label is on a ui thread, you need to [invoke it](http://stackoverflow.com/questions/5192169/update-gui-using-backgroundworker?rq=1) – Sayse Aug 28 '13 at 21:18
  • 3
    I don't think you're going to be able to report progress the way you might think using `File.Move()`. Whatever your `BackgroundWorker()` does it needs to be able to periodically call `ReportProgress()` http://msdn.microsoft.com/en-us/library/ka89zff4.aspx – David Tansey Aug 28 '13 at 21:19
  • Can you please show me some code to do that ? – devavx Aug 28 '13 at 21:19
  • 2
    This article might help: http://www.listener.com.ba/thepage/2011/01/22/copy-files-in-c-with-progress-report/ . The author shows code that breaks the File move (or copy in his case) into smaller chunks, allowing him to get control and call `ReportProgress()` after each chunk. Hope that helps. – David Tansey Aug 28 '13 at 21:25

1 Answers1

2

First, your BackgroundWorker is trying to update the UI from its background thread, which is a no-no in multithreaded UI apps.

To update the UI you'll need it to switch to the UI thread to do the update. This is done by first checking if the label3.InvokeRequired is true (indicating you can't update the UI from the current thread), then passing a delegate to label3.Invoke (not Delegate.Invoke())

This is a pattern you need to be very familiar with for WinForms development. The Control.Invoke MSDN page includes a sample of leveraging this pattern.

Second, you need to call BackgroundWorker.ReportProgress() periodically, which fires the ProgressChanged event with a percentage-complete value.


Here's a sample app. It assumes you've got a Form with a Button, a Label, and a BackgroundWorker with WorkgerReportsProgress = true (and WorkerSupportsCancellation = true, for bonus points).

When you click the button it starts a 5-second blocking task, then switches to 10 1-second blocking tasks (reporting progress along the way).

Note the helper method InvokeIfRequired() that ensures the UI is updated from the correct thread. There's no harm in using this excessively.

public partial class Form1 : Form
{
  public Form1()
  {
    InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
    if (backgroundWorker1.IsBusy)
    {

      label1.Text = "Reset!";
      backgroundWorker1.CancelAsync();
      return;
    }

    backgroundWorker1.RunWorkerAsync();
  }

  private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
  {
    label1.InvokeIfRequired(() => label1.Text = "Gettin busy");
    System.Threading.Thread.Sleep(5000);

    for (int i = 0; i < 10; i++)
    {
      backgroundWorker1.ReportProgress(i*10);
      System.Threading.Thread.Sleep(1000);
    }
  }

  private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  {
    label1.InvokeIfRequired(() => label1.Text = "Done");
  }

  private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
  {
    label1.InvokeIfRequired(() => label1.Text = string.Format("{0}% done", e.ProgressPercentage));
  }
}

public static class InvokeExtensions
{
  public static void InvokeIfRequired(this ISynchronizeInvoke control, MethodInvoker action)
  {
    if (control.InvokeRequired)
    {
      control.Invoke(action, null);
    }
    else
    {
      action();
    }
  }
}
STW
  • 44,917
  • 17
  • 105
  • 161
  • -1 *Your BackgroundWorker is trying to update the UI from its background thread*: **NO** Make UI changes in `ProgressChanged` Event is how backgroundworker is supposed to work. As @David Dansey noticed, since the File.move() method does not report progress (it will not call the `reportProgress` method itself!) there is no progress... – Chris Aug 28 '13 at 21:42
  • Updated to address both issues. The sample stays responsive and reports progress throughough – STW Aug 28 '13 at 22:00
  • 1
    Thanks to take into account my comment :) There is still something that disturbs me. I agree with you when sou say *Your BackgroundWorker is trying to update the UI from its background thread* for the piece of code that try to update `label.text` in `DoWork` method, and so using an InvokeExtension makes sense **in this case**. But from the documentation, the `ProgressChanged` and `RunWorkerCompleted` events are designed to *communicate to the user interface* so I guess it is in the UI thread. I have never used `Invoke` in my backgroundworker implentations. – Chris Aug 29 '13 at 22:00