Syler
Syler

Reputation: 1171

Why does the BackgroundWorker in WPF need Thread.Sleep to update UI controls?

namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
    BackgroundWorker bgWorker;
    Action<int> myProgressReporter;

    public Window1()
    {
        InitializeComponent();
        bgWorker = new BackgroundWorker();
        bgWorker.DoWork += bgWorker_Task;
        bgWorker.RunWorkerCompleted += myWorker_RunWorkerCompleted;

        // hook event to method
        bgWorker.ProgressChanged += bgWorker_ReportProgress;

        // hook the delegate to the method
        myProgressReporter = updateProgress;

        bgWorker.WorkerReportsProgress = true;

    }

    private void myWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        object result;
        result = e.Result;
        MessageBox.Show(result.ToString());
        progressBar1.Value = 0;
        button1.IsEnabled = true;
    }

    private void bgWorker_ReportProgress(object sender, ProgressChangedEventArgs e)
    {
        System.Windows.Threading.Dispatcher disp = button1.Dispatcher;
        disp.BeginInvoke(myProgressReporter,e.ProgressPercentage);
        //Dispatcher.BeginInvoke(myProgressReporter, DispatcherPriority.Normal, e.ProgressPercentage);
    }

    private void updateProgress(int progressPercentage)
    {
        progressBar1.Value = progressPercentage;
    }

    private void bgWorker_Task(Object sender, DoWorkEventArgs e)
    {
        int total = 1000;
        for (int i = 1; i <= total; i++)
        {
            if (bgWorker.WorkerReportsProgress)
            {
                int p = (int)(((float)i / (float)total) * 100);
                bgWorker.ReportProgress(p);
            }
            Thread.Sleep(1); // Without Thread.Sleep(x) the main thread freezes or gives stackoverflow exception, 
        }

        e.Result = "Completed";
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {            
        if(!bgWorker.IsBusy)
            bgWorker.RunWorkerAsync("This is a background process");
        button1.IsEnabled = false;
    }
}
}

Upvotes: 3

Views: 3896

Answers (2)

Henk Holterman
Henk Holterman

Reputation: 273864

Because in your (artificial) scenario you pump 1000 request-for-update to the main thread.
It gets no time to do an idle loop (necessary to do a screen update).

But (thanks to TerrorAustralis), you should start with merging your bgWorker_ReportProgress and myProgressReporter methods. You are now synchronizing twice, a possible cause of stackoverflow. Dispatching the UpdateProgress events is one of the main features of the Backgroundworker:

private void bgWorker_ReportProgress(object sender, ProgressChangedEventArgs e)
{
  //System.Windows.Threading.Dispatcher disp = button1.Dispatcher;
  //disp.BeginInvoke(myProgressReporter,e.ProgressPercentage);
  progressBar1.Value = progressPercentage; // safe because we're on the main Thread here
}

Upvotes: 5

TerrorAustralis
TerrorAustralis

Reputation: 2923

Posibility:
Dispatcher.BeginInvoke() is an Asynchronous operation. Since this is the case, you are able to try to hit it again before it completes its operation. To see if this is the problem, try Dispatcher.Invoke() which is Synchronous

As a possible workaround, if you just want to update the progress bar, the backgroundWorker ProgressChanged event could do this without the use of an explicit dispatcher.

Upvotes: 3

Related Questions