开发者

How to cancel BackgroundWorker in WPF without DoEvents

I have a search box that works great in WinForms, but is giving me trouble in WPF.

It works by starting a search each time a letter is pushed, similar to Google.

    If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then
        If SearchWorker.IsBusy Then
  开发者_如何学Python          SearchWorker.CancelAsync()
            Do While SearchWorker.IsBusy
                'Application.DoEvents() 
                'System.Threading.Thread.Sleep(500)
            Loop
        End If
        doSearchText = txtQuickSearch.Text
        SearchWorker.RunWorkerAsync()
    End If

Every time a key is pushed it cancels the current searchWorker then restarts it. In WinForms the Do while searchworker.isbusy doevents loop worked great, but since I don't have access to that anymore, I need to figure out a better way to do it. Sleep() deadlocks it, and I've tried just doing i+=1 as a way to pass time until it's not busy, but that doesn't work either...

What should I do?

Update: Here's what I changed it to. It works, but the cancel part doesn't seem to ever trigger, this doesn't seem to be running async...

Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports System.Threading
Imports System.Threading.Tasks

Public Class QuickSearch
    Private doSearchText As String
    Private canceled As Boolean
    Private curSearch As String
    Dim searchResults As New ObservableCollection(Of ocSearchResults)

    'Task Factory
    Private cts As CancellationTokenSource
    Private searchtask As Task(Of ObservableCollection(Of ocSearchResults))

    Private Sub txtQuickSearch_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles txtQuickSearch.KeyDown
        If e.Key = Key.Enter Then
            curSearch = ""
        End If
        If ((txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter)) And Not txtQuickSearch.Text = curSearch Then
            If Not cts Is Nothing Then
                cts.Cancel()
                ColorChecker.CancelAsync()
                Try
                    ' searchtask.Wait()
                Catch ex As AggregateException
                    MsgBox(ex.InnerException.Message) 
                End Try
                cts = New CancellationTokenSource
            Else
                cts = New CancellationTokenSource
            End If
            Dim cToken = cts.Token
            Me.Height = 400
            doSearchText = txtQuickSearch.Text
'This always completes fully before continuing on to tRunWorkerComplete(searchtask.Result) '
            searchtask = Task(Of ObservableCollection(Of ocSearchResults)).Factory.StartNew(Function() tPerformSearch(cToken), cToken)
            Try
                tRunWorkerCompleted(searchtask.Result)
            Catch ex As AggregateException
                ' MsgBox(ex.InnerException.Message) 
            End Try
        Else
            If Not cts Is Nothing Then
                cts.Cancel()
            End If
            searchResults.Clear()
        End If
    End Sub


    Function tPerformSearch(ByVal ct As CancellationToken) As ObservableCollection(Of ocSearchResults)
        On Error GoTo sError

        canceled = False
        If curSearch = doSearchText Then
            canceled = True
            Return Nothing
        End If
        curSearch = doSearchText
        Dim SR As New ObservableCollection(Of ocSearchResults)

        Dim t As ocSearchResults
        Dim rSelect As New ADODB.Recordset
        Dim sSql As String = "SELECT DISTINCT CustomerID, CustomerName, City, State, Zip FROM qrySearchFieldsQuick WHERE "
        Dim sWhere As String = "CustomerName Like '" & doSearchText & "%'" 

        SR.Clear()

        With rSelect
            .Open(sSql & sWhere & " ORDER BY CustomerName", MyCn, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockReadOnly)
            Do While Not .EOF 
                If ct.IsCancellationRequested Then ' This never shows true, the process always returns a value, as if it wasn't async'
                    canceled = True
                    Return Nothing
                End If

                Do While IsDBNull(.Fields("CustomerID").Value)
                    .MoveNext()
                Loop

                t = New ocSearchResults(.Fields!CustomerID.Value, .Fields!CustomerName.Value.ToString.Trim, .Fields!City.Value.ToString.Trim, .Fields!State.Value.ToString.Trim, .Fields!Zip.Value.ToString.Trim)
                If Not SR.Contains(t) Then
                    SR.Add(t)
                End If
aMoveNext:
                .MoveNext()
            Loop
            .Close()
        End With

        Return SR
        Exit Function
sError:
        MsgBox(ErrorToString, MsgBoxStyle.Exclamation)
    End Function

    Sub tRunWorkerCompleted(ByVal SR As ObservableCollection(Of ocSearchResults))
        If canceled Then
            Exit Sub
        End If
        If cts.IsCancellationRequested Then
            Exit Sub
        End If

        searchResults.Clear()
        For Each t As ocSearchResults In SR
            searchResults.Add(t)
        Next

        ColorChecker = New BackgroundWorker
        ColorChecker.WorkerReportsProgress = True
        ColorChecker.WorkerSupportsCancellation = True
        ColorChecker.RunWorkerAsync(searchResults)

        lblRecordCount.Text = "(" & searchResults.Count & ") Records"

        progBar.Value = 100
        Exit Sub
sError:
        MsgBox(ErrorToString)
    End Sub


I don't know enough VB to give you any well written sample code, but if you're on .Net 4.0 I suggest switching to the System.Threading.Tasks namespace, which has cancellation abilities.

If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then
    If TokenSource Is Not Nothing Then
        TokenSource.Cancel()
        TokenSource = New CancellationTokenSource()
    End If
    Task.Factory.StartNew(SomeSearchMethod, txtQuickSearch.Text, TokenSource.Token)
End If


I am not sure a BackgroundWorker is flexible enough to provide an elegant solution for this type of background processing anyway. I think what I would do is to create a single dedicated thread for doing the searching. This thread would use the producer-consumer pattern for accepting work items and processing them.

The following code is a rough sketch of how I see this strategy working. You would call the SearchAsync method to request a new search. That method accepts a callback that gets invoked when and if the search operation found something. Notice that the consumer code (in the Run method) cancels its current search operation if another search request is queued. The effect is that the consumer only ever processes the latest request.

Public Class Searcher

  Private m_Queue As BlockingCollection(Of WorkItem) = New BlockingCollection(Of WorkItem)()

  Public Sub New()
    Dim t = New Thread(AddressOf Run)
    t.IsBackground = True
    t.Start()
  End Sub

  Public Sub SearchAsync(ByVal text As String, ByVal callback As Action)
    Dim wi = New WorkItem()
    wi.Text = text
    wi.Callback = callback
    m_Queue.Add(wi)
  End Sub

  Private Sub Run()
    Do While True
      Dim wi As WorkItem = m_Queue.Take()
      Dim found As Boolean = False
      Do While Not found AndAlso m_Queue.Count = 0
        ' Continue searching using your custom algorithm here.
      Loop
      If found Then
        wi.Callback()
      End If
    Loop
  End Sub

  Private Class WorkItem
    Public Text As String
    Public Callback As Action
  End Class

End Class

Here is where the elegance happens. Look at how you can implement the logic from the UI thread now.

If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then
    searcher.SearchAsync(txtQuickSearch.Text, AddressOf OnSearchComplete)
End If

Note that OnSearchComplete will be executed on the worker thread so you will need to call Dispatcher.Invoke from within the callback if you want to publish the results to a UI control.


You can simulate a DoEvents in WPF by doing (in C#):

Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => {}));

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜