Хотите вызывать один и тот же BackgroundWorker несколько раз без использования Application.DoEvents - PullRequest
3 голосов
/ 05 марта 2010

Я столкнулся с проблемой, которую мне удалось исправить с помощью Application.DoEvents, но я не хочу оставлять это, потому что это может привести к всевозможным неприятным проблемам.

Справочная информация: НашиПриложение - это, прежде всего, приложение для настольного компьютера, которое выполняет множество вызовов веб-службы.Мы контролируем все, но изменения в общем дизайне системы не будут рассматриваться всерьез.Один из этих вызовов, Calculate, используется очень часто, и иногда может потребоваться несколько минут для обработки всех данных, чтобы получить действительные результаты.

Ранее этот вызов Calculate выполнялся синхронно, и, таким образом, пользовательский интерфейс блокировался, что заставляло пользователя задаться вопросом, зависло ли приложение или нет, и т. Д. Я успешно переместил все вызовы длительного ожидания в BackgroundWorker, а затемсделал простой экран Ожидания, который будет циклически проходить через анимированное сообщение «Расчет ...».

Теперь проблема возникает, когда наш код пользовательского интерфейса пытается снова вызвать процедуру вычисления перед завершением первой.Я получил бы сообщение «Этот BackgroundWorker в настоящее время занят и не может запускать несколько экземпляров ...».То, что я думал, должно контролироваться вызовами resetEvent.WaitOne ().Это не так, я подумал, что может помочь другое событие, контролирующее доступ ко всей подпрограмме, поэтому я добавил calcDoneEvent.Это по-прежнему не решало проблему, но приводило к тому, что оно блокировалось на неопределенный срок при втором вызове CalcЛoneEvent.WaitOne () в Calculate.Затем по какой-то причине я добавил Application.DoEvents в конец Calculate и альта, проблема решена.

Я не хочу оставлять это. DoEvents там, потому что я прочитал, что это может вызвать проблемы, которые позжеочень трудно выследить.Есть ли лучший способ справиться с этой ситуацией?

Заранее спасибо ..

Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker
Dim resetEvent As New Threading.AutoResetEvent(False)
Dim calcDoneEvent As New Threading.AutoResetEvent(True)

Public Sub Calculate()

    calcDoneEvent.WaitOne() ' will wait if there is already a calculate running.'
    calcDoneEvent.Reset()

    ' setup variables for the background worker'

    CalculateBGW.RunWorkerAsync() ' Start the call to calculate'

    Dim nMsgState As Integer = 0
    ' will block until the backgorundWorker is done'
    Do While Not resetEvent.WaitOne(200) ' sleep for 200 miliseconds, then update the status window'
        Select Case nMsgState
            Case 1
                PleaseWait(True, vbNull, "Calculating.   ")
            Case 2
                PleaseWait(True, vbNull, "Calculating..  ")
            Case 3
                PleaseWait(True, vbNull, "Calculating... ")
            Case 4
                PleaseWait(True, vbNull, "Calculating....")
            Case Else
                PleaseWait(True, vbNull, "Calculating    ")
        End Select
        nMsgState = (nMsgState + 1) Mod 5
    Loop

    PleaseWait(False, vbNull) 'make sure the wait screen goes away'

    calcDoneEvent.Set() ' allow another calculate to proceed'
    Application.DoEvents() ' I hate using this here'
End Sub

Private Sub CalculateBGW_DoWork(ByVal sender As System.Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) Handles CalculateBGW.DoWork
    Try
        'make WS Call, do data processing on it, can take a long time..'
        'No Catch inside the DoWork for BGW, or exception handling wont work right...'
        'Catch'
    Finally
        resetEvent.Set() 'unblock the main thread'
    End Try
End Sub

Private Sub CalculateBGW_RunWorkerCompleted(ByVal sender As Object, _
    ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles CalculateBGW.RunWorkerCompleted

    'If an error occurs we must check e.Error prior to touching e.Result, or the BGW' 
    'will possibly "eat" the exception for breakfast (I hear theyre tasty w/ jam)'
    If Not (e.Error Is Nothing) Then

        'If a Web Exception timeout, retry the call'
        If TypeOf e.Error Is System.Net.WebException And _
            e.Error.Message = "The operation has timed out" And _
            intRetryCount < intRetryMax Then

            ' Code for checking retry times, increasing timeout, then possibly recalling the BGW'

            resetEvent.Reset()
            CalculateBGW.RunWorkerAsync() 'restart the call to the WS'
        Else
            Throw e.Error ' after intRetryMax times, go ahead and throw the error up higher'
        End If
    Else
        Try

            'normal completion stuff'

        Catch ex As Exception
            Throw
        End Try
    End If

End Sub

1 Ответ

4 голосов
/ 05 марта 2010

Вы заявили:

Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker
Dim resetEvent As New Threading.AutoResetEvent(False)
Dim calcDoneEvent As New Threading.AutoResetEvent(True)

как личные поля содержащего класса. Обратите внимание, что таким образом все вызовы RunWorkerAsync() относятся к одному и тому же экземпляру объекта класса BackgroundWorker (то есть к одному и тому же объекту). Вот почему он "занят". Этот код создан для хранения только одного BackgroundWorker в данный момент времени.

Если вы хотите, чтобы код пользовательского интерфейса вызывал метод Calculate() всякий раз, когда это необходимо, вы должны объявить CalculateBGW как локальную переменную в методе Calculate (), создавая тем самым новый экземпляр класса BackgroundWorker при каждом вызове. (и они будут работать асинхронно). Это означает, что вам придется добавлять и удалять обработчики событий внутри Calculate (), используя AddHandler и RemoveHandler.

Существует несколько подходов к обновлению пользовательского интерфейса в ходе выполнения, но предлагается использовать событие BackgroundWorker.ProgressChanged и метод BackgroundWorker.ReportProgress.

Используйте событие BackgroundWorker.RunWorkerCompleted в качестве триггера обратного вызова, сообщая пользовательскому интерфейсу о завершении вычисления, таким образом вызывая необходимый код для представления результата. Такой подход устраняет необходимость поддерживать зацикливание потока вокруг направления потока вычисления - тем самым устраняя необходимость в DoEvents(). Он позволяет потоку вычислений информировать своего босса о завершении работы, вместо того, чтобы босс проверял статус работника и снова и снова засыпал.

...