Как отменить BackgroundWorker в WPF без DoEvents - PullRequest
3 голосов
/ 02 июня 2011

У меня есть окно поиска, которое отлично работает в 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 ]

2 голосов
/ 02 июня 2011

Я не уверен, что 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

Вот здесь и происходит элегантность. Посмотрите, как вы можете реализовать логику из потока пользовательского интерфейса.

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

Обратите внимание, что OnSearchComplete будет выполняться в рабочем потоке, поэтому вам нужно будет вызвать Dispatcher.Invoke из обратного вызова, если вы хотите опубликовать результаты в элементе управления пользовательского интерфейса.

2 голосов
/ 02 июня 2011

Я не знаю достаточно VB, чтобы дать вам какой-либо хорошо написанный пример кода, но если вы используете .Net 4.0, я предлагаю переключиться на пространство имен System.Threading.Tasks, которое имеет отмену способности .

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
0 голосов
/ 16 июня 2011

Вы можете смоделировать DoEvents в WPF, выполнив (в C #):

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

...