Home > Software engineering >  How to stop background worker in WPF?
How to stop background worker in WPF?

Time:11-12

I am using an MVVM model in my WPF application. I have an command binding to the cancel button. I have a start button which starts a few background workers. When i click on the cancel button, i want all the background workers to stop/quit. With my current code when i click on cancel button, the background worker does not stop and the "StartEngineeringOperation" finishes. Can anyone please help me out with what i am doing wrong here?

Current code:

For EngineeringViewModel.cs:

public class EngineeringViewModel{

public EngineeringViewModel()
{
            StartEngineering= new DelegateCommand(o =>
            {
                worker = new BackgroundWorker
                {
                    WorkerReportsProgress = true,
                    WorkerSupportsCancellation = true
                };
                worker.ProgressChanged  = Worker_ProgressChanged;
                worker.RunWorkerCompleted  = worker_RunWorkerCompleted;
                if (worker.IsBusy != true) worker.RunWorkerAsync();
                worker.DoWork  = (s, e) =>
                {
                    StartEngineeringOperation();
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        return;
                    }
                };
            },
                k => true);
            Cancel = new DelegateCommand(CancelEngineeringOperation);
}

private void StartEngineeringOperation()
   {
      startAlarmService();
      startTrendQualityCheck();
   }

private void CancelEngineeringOperation(object param)
    {           
        worker.DoWork  = (s, e) =>
        {
            if (worker.IsBusy)
            {
                worker.CancelAsync();
                e.Cancel = true;
                return;
            }
           
        };
       
    }
}

I tried this : but doesn't seem to work:

private void StartEngineeringOperation()
   {
      startAlarmService();                                                                                                      
           if (worker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
      startTrendQualityCheck();
   }

CodePudding user response:

As you may have learned from te comments, you need to poll the state of the BackgroundWorker in your operations that you want to support cancellation. Then take measures to cancel the ongoing operation gracefully.

The example shows how to cancel a background thread on button click. The first example uses the old BackgroundWorker and the second the modern and cleaner Task library.

BackgroundWorker

private BackgroundWorker Worker { get; set; }

private void StartWorker()
{
  this.Worker = new BackgroundWorker
  {
    WorkerReportsProgress = true,
    WorkerSupportsCancellation = true
  };
   
  this.Worker.DoWork  = BackgroundWorker_DoWork;
}

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
  BackgroundWorker worker = sender as BackgroundWorker;

  DoCancellableWork();  

  // Stop BackgroundWorker from executing
  if (worker.CancellationPending)
  {
    e.Cancel = true;
  }     
}

private void DoCancellableWork()
{      
  // Check for cancellation before executing the cancellable operation and allocating resources etc..
  if (this.Worker.CancellationPending)
  {
    return;
  }

  // Periodically/regularly check for the cancellation flag
  for (int i = 0; i <= 10000000000; i  )
  {
    if (this.Worker.CancellationPending)
    {
      // Cancel operation gracefully e.g., do some cleanup, free resources etc.

      return;
    }

    // Do some work
  }
}

// Alternatively use a command e.g., in a view model class
private void CancelBackgroundWorker_Click(object sender, EventArgs e)
{
  if (this.Worker.WorkerSupportsCancellation)
  {
    this.Worker.CancelAsync();
  }
}

Task library

The example uses Progress<T> to report progress from the background thread to the UI thread.

private CancellationTokenSource CancellationTokenSource { get; set; }

private async Task StartWorker()
{
  this.CancellationTokenSource = new CancellationTokenSource();

  // Prepare callback to update UI from the background thread.
  // The Progress<T> instance MUST be created on the UI thread
  IProgress<int> progressReporter = new Progress<int>(progress => this.ProgressBar.Value = progress);

  await Task.Run(
    () => DoWork(progressReporter, this.CancellationTokenSource.Token), 
    this.CancellationTokenSource.Token);

  this.CancellationTokenSource.Dispose();
}

private void DoWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
  DoCancellableWork(progressReporter, cancellationToken);
}

private void DoCancellableWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
  // Check for cancellation before executing the operation and allocating resources etc..
  if (cancellationToken.IsCancellationRequested)
  {
    return;
  }

  // Periodically/regularly check for the cancellation flag
  for (int i = 0; i <= 10000000000; i  )
  {
    if (cancellationToken.IsCancellationRequested)
    {
      // Cancel operation gracefully e.g., do some cleanup, free resources etc.

      return;
    }

    // Do some work

    // Report progress
    progressReporter.Report(20);
  }
}

// Alternatively use a command e.g., in a view model class
private void CancelBackgroundThread_Click(object sender, EventArgs e)
{
  this.CancellationtokenSource?.Cancel();
}

CodePudding user response:

Since the OP describes the task being done as "checking services", I would assume the work done looks something like this:

while(true){
    // check service
    // Post result back to UI thread
    Thread.Sleep(...);
}

This is not the best way to write such such a check. As in most cases where Thread.Sleep is used, a timer would be a better alternative:

var myTimer  = new System.Timers.Timer(...);
myTimer .Elapsed  = OnTimedEvent;
myTimer .AutoReset = true;
myTimer .Enabled = true;

...

private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
    // check service
    // Post result back to UI thread
}

This makes the problem of stopping/starting the task being done a simple matter of changing the Enabled-flag of the timer. It is also possible to use a timer, or a synchronization context to run the event directly on the UI thread, this is probably the best solution if "checking services" only takes a few ms.

  • Related