Why BackgroundWorker always is busy?
Asked Answered
D

5

12

I realized something strange in my background worker in my WPF application.

What I'm trying to accomplish right now is to wait until the BW finishes to start another thread.

Check the following code:

if (bw.IsBusy)
{
    bw.CancelAsync();

    System.Threading.ThreadStart WaitThread = 
        new System.Threading.ThreadStart(delegate() {
            while (bw.IsBusy)
            {
                System.Threading.Thread.Sleep(100);
            }

            bw.RunWorkerAsync();
        });

    System.Windows.Application.Current.Dispatcher.Invoke(
        System.Windows.Threading.DispatcherPriority.Normal,
        WaitThread);  // if I remove this line, bw fires RunWorkerAsyncEvent
}
else
{
    bw.RunWorkerAsync();
}

Please note that I added a Dispatcher.Invoke to wait until the bw is not busy, but ALL THE TIME IS BUSY if I Invoke it and never fires the RunWorkerAsyncCompleted event. Although,, if I remove that line, FIRES the event and it's really strange that.

How can I wait until the bw finishes?

Doorstone answered 21/1, 2013 at 0:34 Comment(1)
It is not clear, what line you remove, and what changes then...could you explain it more carefully?Fleischman
W
32

This is called "deadlock", a very common threading problem. The BGW cannot stop being busy until the RunWorkerCompleted event runs. That event runs on your app's main thread, it can only run when your main thread isn't busy doing something else. It has to be idle, running inside the dispatcher loop.

But your main thread isn't idle, it is stuck inside the while() loop waiting for IsBusy to return false. So the event can never run since the main thread is busy waiting for the BGW to complete, the BGW cannot complete because the main thread never goes idle. "Deadly embrace", aka deadlock.

You will have to do this differently, you cannot wait. Say by creating another instance of BGW. Or moving the code after the wait into the RunWorkerCompleted event handler. If it is activated by, say, a button's Click event then be sure to disable the button when call RunWorkerAsync(), re-enable it in RunWorkerCompleted.

Wellfed answered 21/1, 2013 at 1:25 Comment(2)
Do I have another option to check until my BGW finishesDoorstone
Another dirty solution: If the waiting loop is in MainThread: Call " Application.DoEvents();" in the loop. This will also solve the Deadlock.Idiophone
D
6

The deadlock occurs due to the "main" thread of the application can not run (and handle events), thus the Backgroundworker is never able to fire it's finished-function. What you can do to allow the application to fire this is add an Application.DoEvents();

This is what I currently use in a similiar scenario, and it appears to work like a charm!

    while (bw.IsBusy)
    {
        Application.DoEvents();
        System.Threading.Thread.Sleep(100);
    }
Disini answered 15/7, 2016 at 9:29 Comment(1)
Is there a WPF native version of Application.DoEvents? That namespace is usually associated with winforms and isn't included normally in a WPF app.Identification
K
3

I'm not sure if I understood correctly, but I think you're are trying to restart the BackgroundWorker (so if it's busy, you stop it first and then you start it again), if this is what you want, try this:

declare this delegate somewhere in your class:

    private delegate bool StateChecker();

and use this code to restart your BackgroundWorker:

    StateChecker stillWorking = () => { return bw.IsBusy; };

    if (bw.IsBusy)
    {
        bw.CancelAsync();
        while ((bool)this.Dispatcher.Invoke(stillWorking, null)) Thread.Sleep(15);
    }
    bw.RunWorkerAsync();
Keyek answered 21/1, 2013 at 6:58 Comment(1)
What is the dispatcher in question?Kilmer
C
2

Here is full and working code.

C# (window's class)

 public partial class ForceSyncWindow : Window
    {
        BackgroundWorker backgroundWorker = new BackgroundWorker();

        public ForceSyncWindow()
        {
            InitializeComponent();

            ProgressBar1.Visibility = System.Windows.Visibility.Hidden;

            backgroundWorker.WorkerSupportsCancellation = true;

            // To report progress from the background worker we need to set this property
            backgroundWorker.WorkerReportsProgress = true;

            // This event will be raised on the worker thread when the worker starts
            backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);

            // This event will be raised when we call ReportProgress
            backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);

            backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
        }

        void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // First, handle the case where an exception was thrown. 
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else if (e.Cancelled)
            {
                // Next, handle the case where the user canceled  
                // the operation. 
                // Note that due to a race condition in  
                // the DoWork event handler, the Cancelled 
                // flag may not have been set, even though 
                // CancelAsync was called.
                ProgressBar1.Value = 0;
                // TODO LOG  = "Canceled";

            }
            else
            {
                // Finally, handle the case where the operation  
                // succeeded.
                // TODO LOG e.Result.ToString();

            }

            ProgressBar1.Value = 0;
            ProgressBar1.Visibility = System.Windows.Visibility.Hidden;

            // Enable the Synchronize button. 
            this.Synchronize.IsEnabled = true;

            // Disable the Cancel button.
            this.Cancel.IsEnabled = false;
        }

        // On worker thread so do our thing!
        void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
              // Your background task goes here
                for (int i = 0; i <= 100; i++)
                {
                    if (backgroundWorker.CancellationPending == true)
                    {
                        e.Cancel = true;
                        break;
                    }
                    else
                    {
                        // Perform a time consuming operation and report progress.
                        // Report progress to 'UI' thread
                        backgroundWorker.ReportProgress(i);
                        // Simulate long task
                        System.Threading.Thread.Sleep(100); ;
                    }                              
                }            
        }

        // Back on the 'UI' thread so we can update the progress bar
        void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // The progress percentage is a property of e
            ProgressBar1.Value = e.ProgressPercentage;
        }

        private void Synchronize_Click(object sender, RoutedEventArgs e)
        {
            ProgressBar1.Value = 0;
            ProgressBar1.Visibility = System.Windows.Visibility.Visible;

            // Disable the Synchronize button. 
            this.Synchronize.IsEnabled = false;

            // Enable the Cancel button.
            this.Cancel.IsEnabled = true;

            // Start the background worker
            backgroundWorker.RunWorkerAsync();
        }

        private void Cancel_Click(object sender, RoutedEventArgs e)
        {
            if (backgroundWorker.IsBusy)
            {
                // Cancel the background worker
                backgroundWorker.CancelAsync();
            }
        }
    }

XAML

<Window x:Class="MySyncManager.Views.ForceSyncWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="ForceSyncWindow" Height="300" Width="509" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
    <Grid>

        <Button Content="Synchronize" Name="Synchronize" HorizontalAlignment="Left" Margin="411,10,0,0" VerticalAlignment="Top" Width="75" Click="Synchronize_Click"/>
        <RichTextBox HorizontalAlignment="Left" Height="132" Margin="10,116,0,0" VerticalAlignment="Top" Width="476">

        </RichTextBox>
        <Button Content="Cancel" x:Name="Cancel" HorizontalAlignment="Left" Margin="411,40,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.508,2.154" Click="Cancel_Click"/>
        <ProgressBar Name="ProgressBar1" HorizontalAlignment="Left" Height="10" Margin="10,101,0,0" VerticalAlignment="Top" Width="476"/>

    </Grid>
</Window>
Czarina answered 31/7, 2014 at 16:29 Comment(0)
G
-1

Put it inside of another thread:

private void myButton_Click(object sender, RoutedEventArgs e)
{
   BackgroundWorker mainWorker = new BackgroundWorker();
   mainWorker.DoWork += new DoWorkEventHandler(mainWorker_DoWork);
   mainWorker.RunWorkerAsync();
}

void mainWorker_DoWork(object sender, DoWorkEventArgs e)
{
   for(int x = 0; x >= 100; x++)
   {
      BackgroundWorker worker = new BackgroundWorker();
      worker.DoWork += new DoWorkEventHandler(worker_DoWork);
      worker.RunWorkerAsync(argument: x);
      while(worker.IsBusy)
      {
         Thread.Sleep(100);
      }
   }
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
   string iam = "Hello i'm thread number: " + e.Argument.ToString();
   //do something ...
}
Gravitative answered 14/10, 2020 at 3:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.