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

如何在没有DoEvents的情况下取消WPF中的BackgroundWorker,wpf,vb.net,multithreading,backgroundworker,Wpf,Vb.net,Multithreading,Backgroundworker,我有一个搜索框,它在WinForms中工作得很好,但在WPF中给我带来了麻烦 它的工作原理是每次推送一封信时就开始搜索,类似于谷歌 If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then If SearchWorker.IsBusy Then SearchWorker.CancelAsync() Do While SearchWorker.IsB

我有一个搜索框,它在WinForms中工作得很好,但在WPF中给我带来了麻烦

它的工作原理是每次推送一封信时就开始搜索,类似于谷歌

    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循环
工作得很好,但由于我再也无法访问该循环,我需要找到更好的方法。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

我知道的VB不够多,无法给您提供任何编写良好的示例代码,但是如果您使用的是.NET4.0,我建议您切换到名称空间,因为名称空间已经存在


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

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

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
将在工作线程上执行,因此您需要调用
Dispatcher。如果要将结果发布到UI控件,请从回调中调用

您可以在WPF中模拟DoEvents(在C中):


Invoke(DispatcherPriority.Background,新操作(()=>{}))

看起来真的很有趣,但是它可以随意停止和重新启动吗,或者它必须等到完成吗?@AndyD273:是的,但是您的自定义搜索算法必须在安全点手动轮询此停止/重新启动信息。请注意,在我的示例中,我轮询
m_Queue.Count=0
,以了解是否应取消当前操作。如果它被取消,那么循环只会再次来回摆动,并重新开始这个过程。您是否可以看到这样的效果:只有最近的请求处于活动状态?同样,这只是一个粗略的草图,您必须进行一些非琐碎的修改,以使您的自定义算法适合该模式。这看起来是可行的,但我在将其放入代码中时遇到了问题。我得多读点书。。。不了解如何正确使用StartNew。周末之后。SomeSearchMethod应该是一个接受单个字符串参数的方法。添加了更多代码。它正在工作,但总是返回一个完整的结果,但总是在我键入时开始下一次搜索之前完成。不是异步行为。@AndyD273:我在VB中没有做太多(阅读:任何)工作。我要花点时间来理解你的代码,没关系。我知道有时我也很难在C#和VB之间进行翻译。。。我想你给我指出了正确的方向。
If (txtQuickSearch.Text.Length >= 3) Or (e.Key = Key.Enter) Then
    searcher.SearchAsync(txtQuickSearch.Text, AddressOf OnSearchComplete)
End If