如何在没有DoEvents的情况下取消WPF中的BackgroundWorker

时间:2011-06-02 13:26:06

标签: wpf vb.net multithreading backgroundworker

我有一个在WinForms中运行良好的搜索框,但在WPF中给我带来了麻烦。

每次推送信件时都会启动搜索,类似于Google。

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

每次按下某个键,它都会取消当前的searchWorker,然后重新启动它。在WinForms中Do while searchworker.isbusy doevents loop工作得很好,但由于我无法访问它,我需要找到一个更好的方法来实现它。 Sleep()死了,我试过把i + = 1作为一种通过时间直到它不忙的方式,但这也不起作用...
我该怎么办?

更新:这是我改变它的内容。它工作,但取消部分似乎没有触发,这似乎没有运行异步...

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

3 个答案:

答案 0 :(得分:2)

我不太了解VB给你任何写得很好的示例代码,但如果你使用的是.Net 4.0,我建议切换到System.Threading.Tasks命名空间,该命名空间有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

答案 1 :(得分:2)

我不确定BackgroundWorker是否足够灵活,无论如何都能为这种类型的后台处理提供优雅的解决方案。我想我要做的是创建一个专门的线程来进行搜索。该线程将使用生产者 - 消费者模式来接受工作项并对其进行处理。

以下代码是我如何看待此策略工作的草图。您可以调用SearchAsync方法来请求新搜索。该方法接受在搜索操作找到某些内容时调用的回调。请注意,如果另一个搜索请求排队,则使用者代码(在Run方法中)会取消其当前的搜索操作。结果是消费者只处理最新的请求。

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

这是优雅发生的地方。看看如何从UI线程实现逻辑。

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

请注意,OnSearchComplete将在工作线程上执行,因此如果要将结果发布到UI控件,则需要从回调中调用Dispatcher.Invoke

答案 2 :(得分:0)

您可以通过执行(在C#中)来模拟WPF中的DoEvents:

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