views:

1471

answers:

3

Users of my application type HTML into a TextBox control.

I want my application to validate their input in the background.

Because I don't want to hammer the validation service, I've tried to build in a one-second delay before each validation.

However, I don't seem to be able to correctly interrupt an already-running BackgroundWorker process.

My Visual Basic code:

Sub W3CValidate(ByVal WholeDocumentText As String)

    'stop any already-running validation
    If ValidationWorker.IsBusy Then
        ValidationWorker.CancelAsync()
        'wait for it to become ready
        While ValidationWorker.IsBusy
            'pause for one-hundredth of a second
            System.Threading.Thread.Sleep(New TimeSpan(0, 0, 0, 0, 10))
        End While
    End If

    'start validation
    Dim ValidationArgument As W3CValidator = New W3CValidator(WholeDocumentText)
    ValidationWorker.RunWorkerAsync(ValidationArgument)

End Sub

It seems that after calling my BackgroundWorker's CancelAsync(), its IsBusy never becomes False. It gets stuck in an infinite loop.

What am I doing wrong?

A: 

I found the answer in this article:

BackgroundWorker Closure and Overridable Task by Patrick Smacchia

I've adapted his code:

Private _ValidationArgument As W3CValidator

Sub W3CValidate(ByVal WholeDocumentText As String)
    If _ValidationArgument IsNot Nothing Then
        _ValidationArgument = New W3CValidator(WholeDocumentText)
        Exit Sub
    End If
    If Not ValidationWorker.IsBusy Then
        ValidationWorker.RunWorkerAsync(New W3CValidator(WholeDocumentText))
        Exit Sub
    End If
    _ValidationArgument = New W3CValidator(WholeDocumentText)
    ValidationWorker.CancelAsync()
    Dim TimerRetryUntilWorkerNotBusy As New Windows.Threading.DispatcherTimer
    AddHandler TimerRetryUntilWorkerNotBusy.Tick, AddressOf WorkTicker
    TimerRetryUntilWorkerNotBusy.Interval = New TimeSpan(1) '100 nanoseconds
    TimerRetryUntilWorkerNotBusy.Start()
End Sub

Sub WorkTicker(ByVal sender As Object, ByVal e As System.EventArgs)
    If ValidationWorker.IsBusy Then
        Exit Sub
    End If
    DirectCast(sender, Windows.Threading.DispatcherTimer).Stop()
    ValidationWorker.RunWorkerAsync(_ValidationArgument)
    _ValidationArgument = Nothing
End Sub
Zack Peterson
+1  A: 

In your background worker process loop you need to check for
backgroundWorkerPageProcess.CancellationPending
and exit accordingly. Then once it exists your while loop isBusy should be flagged accordingly.

Update: After you set Cancel = true are you returning out of the method? spitballing here Update 2: You have the WorkerSupportsCancellation flag set to true on the backgroundworker? Also in worker completed method return out if e.Cancelled.... more spitballs

Update 3: after some checking and compilation of my own it appears the damn thing never gets out of isbusy within the same method. -One option is to disable the button while busy and have another to cancel, only for the user to reclick the validation. -Or on your worker completed method if(e.Cancelled) call your validation method with appropriate text....

either way is kind of bust though. Sorry to not be of much help here.

Pat
That's what I expected too. My DoWork() method does set e.Cancel = True if DirectCast(sender, BackgroundWorker).CancellationPending. But IsBusy never returned to False regardless.
Zack Peterson
My DoWork() runs "e.Cancel = True" then immediately "Exit Sub". My RunWorkerCompleted() simply does all it's work inside an If condition: "If Not e.Cancelled Then ... End If End Sub". Must either do more?
Zack Peterson
Yes. I've set ValidationWorker.WorkerSupportsCancellation = True. What do you mean "return out if e.Cancelled"?
Zack Peterson
Yeah. It's pretty unintuitive.
Zack Peterson
+1  A: 

Try something like this:

bool restartWorker = false;

 void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
                    // add other code here
  if (e.Cancelled && restartWorker)
  {
   restartWorker = false;
   backgroundWorker1.RunWorkerAsync();
  }
 }

 private void button1_Click(object sender, EventArgs e)
 {
  if (backgroundWorker1.IsBusy)
  {
   restartWorker = true;
   backgroundWorker1.CancelAsync();
  }
  else
   backgroundWorker1.RunWorkerAsync();
 }