Threaded Function имеет несколько параметров и возвращает данные

Я работаю над приложением WPF .NET 3.5, которое выполняет несколько длинных задач, которые я хотел бы сделать отдельным потоком для потока пользовательского интерфейса для обработки данных, а затем по завершении обновить некоторые метки в пользовательском интерфейсе. Проблема, с которой я сталкиваюсь, заключается в том, что функция, которую я использую, использует два параметра, и я изо всех сил пытаюсь понять, как вызвать функцию с несколькими параметрами в потоке и обновить пользовательский интерфейс.

Я играл с использованием Delegate Sub для вызова функции (она находится в отдельном классе), и мой код также пытался вернуть набор данных из функции для вызывающего потока для обновления пользовательского интерфейса, но я не уверен, что это лучшая практика для достижения этого, или мне следует использовать диспетчер для вызываемой функции для обновления пользовательского интерфейса (обратная связь будет очень признательна).

Мой код выглядит следующим образом.

    Private Delegate Sub WorkHandler(ByVal input1 As String, ByVal input2 As String)
    Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
      Dim test_helper As New test_global
      Dim worker As New WorkHandler(AddressOf test_helper.getWeatherData)
      worker.BeginInvoke("IDA00005.dat", "Adelaide", AddressOf weatherCallBack, Nothing)

      ' The following is what I was using prior to attempting to work with threads, do I continue to update the UI here getting the called function to return a dataset, or do I have the called function do the UI updating?
      'Dim ls As DataSet = test_helper.getWeatherData("IDA00005.dat", "Adelaide")
      'Dim f_date As String = ls.Tables("weather").Rows(1).Item(3).ToString
    End Sub
    Public Sub weatherCallBack(ByVal ia As IAsyncResult)
        CType(CType(ia, Runtime.Remoting.Messaging.AsyncResult).AsyncDelegate, WorkHandler).EndInvoke(ia)
    End Sub

И моя функция, которую я пытаюсь вызвать, выглядит следующим образом:

Class test_global
  Public Sub getWeatherData(ByVal filename As String, ByVal location As String) 'As DataSet
    ...
  End Sub
End Class

Моя проблема заключается в том, что если бы я должен был иметь вызывающий поток для обновления пользовательского интерфейса, как мне заставить вызываемый поток возвращать набор данных или если вызываемый поток должен обновлять пользовательский интерфейс, как мне это сделать?

Обновление:

Следуя предоставленным рекомендациям, я реализовал BackgroundWorker, который вызывает события DoWork и RunWorkerCompleted для получения данных и обновления пользовательского интерфейса соответственно. Мой обновленный код выглядит следующим образом:

Class Weather_test
  Implements INotifyPropertyChanged
  Private WithEvents worker As System.ComponentModel.BackgroundWorker
  Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
  Private Sub NotifyPropertyChanged(ByVal info As String)
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
  End Sub
  Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    Dim test_helper As New test_global
    Dim worker = New System.ComponentModel.BackgroundWorker
    worker.WorkerReportsProgress = True
    worker.WorkerSupportsCancellation = True
    Dim str() = New String() {"IDA00005.dat", "Adelaide"}
    Try
      worker.RunWorkerAsync(str)
    Catch ex As Exception
      MsgBox(ex.Message)
    End Try
  End Sub
  Private Sub worker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles worker.DoWork
    Dim form_Helpder As New test_global
    Dim ds As DataSet = form_Helpder.getWeatherData(e.Argument(0), e.Argument(1))
    e.Result = ds
  End Sub
  Private Sub worker_Completed(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
    If e.Error IsNot Nothing Then
      MsgBox(e.Error.Message)
    Else
      ...
      NotifyPropertyChanged("lbl_minToday")
      ... 
    End If
  End Sub
End Class

Затем у меня есть в отдельном классе мои функции, которые получают и обрабатывают данные.

Я могу отлаживать код в Visual Studio 2010, и форма отображается, но метки не обновляются, и когда я ставлю точку останова в строке RunWorkerAsync, строка вызывается, и подпрограмма Window_Loaded завершается, но кажется, что ни один из DoWork или События RunWorkerCompleted вызываются (хорошо хоть функции нет).

Может ли кто-нибудь помочь в отладке кода, чтобы понять, почему эти функции не вызываются?

Кроме того, является ли приведенный выше код правильным методом, рекомендованным в ответах?

Любая оказанная помощь будет принята с благодарностью.

Мэтт


person Lima    schedule 12.01.2011    source источник
comment
Я обновил вопрос, я реализовал BackgroundWorker в соответствии с рекомендациями, однако у меня возникли проблемы с получением двух функций для обработки событий DoWork и RunWorkerCompleted.   -  person Lima    schedule 13.01.2011


Ответы (3)


Используйте BackgroundWorker. Реализуйте свой долговременный метод и передайте аргументы методу в DoWorkEventArgs параметрах DoWork обработчика событий. Не обновляйте пользовательский интерфейс ни прямо, ни косвенно (т. е. не обновляйте свойства вашей модели представления) в этом методе.

Используйте отчеты о ходе выполнения для обновления пользовательского интерфейса во время выполнения метода: вызовите ReportProgress в методе с длительным выполнением, передав любую информацию, которая должна отображаться в пользовательском интерфейсе, в параметре UserState. В обработчике событий ProgressChanged получите состояние из ProgressChangedEventArgs и обновите пользовательский интерфейс (будем надеяться, обновив соответствующие свойства вашей модели представления и подняв PropertyChanged).

Вам нужно будет реализовать класс, содержащий состояние пользователя для отчетов о ходе выполнения, поскольку UserState имеет тип object.

Обратите внимание, что вы также можете обновить пользовательский интерфейс с результатами длительного метода, когда он завершится. Это делается аналогично отчету о проделанной работе: реализуйте класс, содержащий результаты, установите свойство Result объекта DoWorkEventArgs на экземпляр этого класса, и результат будет доступен в свойстве Result объекта WorkCompletedEventArgs при наступлении события RunWorkerCompleted. Поднялся.

Убедитесь, что вы обрабатываете любые исключения, которые вызывает длительный метод, проверяя свойство Error объекта WorkCompletedEventArgs.

person Robert Rossney    schedule 12.01.2011
comment
Пожалуйста, посмотрите мой обновленный код. Является ли PropertyChanged, о котором вы говорили, тем же самым, что и INotifyPropertyChanged? - person Lima; 13.01.2011
comment
INotifyPropertyChanged — это интерфейс. PropertyChanged это событие. - person Robert Rossney; 14.01.2011
comment
Я отметил это как ответ, поскольку вы предоставили массу информации и указали мне на событие PropertyChanged. Большое спасибо за вашу помощь. - person Lima; 14.01.2011

Вам следует использовать компонент BackgroundWorker.

Вы должны вызвать свою функцию в обработчике DoWork и установить e.Result в возвращаемый набор данных.
Затем вы можете обновить пользовательский интерфейс в обработчике RunWorkerCompleted.

person SLaks    schedule 12.01.2011

У меня нет большого опыта работы с BackgroundWorker (я использовал его только один раз), но это определенно решение вашей проблемы. Однако подход, который я всегда использую, заключается в запуске нового потока (не потока ThreadPool через делегатов), который получает блокировку, а затем обновляет все свойства. При условии, что ваш класс реализует INotifyPropertyChanged, вы можете использовать привязку данных, чтобы графический интерфейс автоматически обновлялся при каждом изменении свойства. У меня были очень хорошие результаты с этим подходом.

Что касается передачи Dispatcher в ваш поток, я считаю, что вы тоже можете это сделать. Тем не менее, я бы поступил осторожно, потому что я полагаю, что сталкивался с такими случаями, когда Dispatcher, который, как мне кажется, я использую, больше не связан с основным потоком. У меня есть библиотека, которой нужно вызвать метод, касающийся элементов графического интерфейса (даже если диалоговое окно может не отображаться), и я решил эту проблему с помощью Dispatcher.Invoke. Я смог гарантировать, что использую Dispatcher, связанный с основным потоком, потому что мое приложение использует MEF для его экспорта.

Если вы хотите получить более подробную информацию о чем-либо, что я опубликовал, пожалуйста, прокомментируйте, и я сделаю все возможное, чтобы приукрасить темы.

person Dave    schedule 12.01.2011
comment
Это не сработает; вы будете обновлять объекты пользовательского интерфейса из потока заднего плана. Кроме того, вы должны использовать ThreadPool. - person SLaks; 12.01.2011
comment
Вы можете обновлять объекты пользовательского интерфейса, когда используете BackgroundWorker, используя его функцию отчетов о ходе выполнения. Редко необходимо явно использовать System.Threading в приложении WPF. - person Robert Rossney; 12.01.2011
comment
@SLaks Вы имеете в виду, что это не сработает, как будто есть более эффективный способ, или вы имеете в виду, что подход полностью ошибочен и не будет работать вообще? Если последнее, то я запутался, потому что у меня всегда есть свойства обновления фоновых потоков, а затем просто привязка данных к этим свойствам из основного графического интерфейса. Не было проблем с потоками (например, обновление из неосновного потока). Почему это должен быть ThreadPool? Если это длительный процесс, то фоновый поток предположительно является правильным способом сделать это. - person Dave; 12.01.2011
comment
@Robert Rossney: единственная причина, по которой я не использую BackgroundWorker, заключается в том, что то, что я делаю в потоках, не связано с прогрессом. Обычно это связано с получением данных телеметрии и их постоянным отображением в окне. - person Dave; 12.01.2011
comment
Отчет о прогрессе — это не просто обновление индикатора выполнения с процентом выполнения. Если вы указываете, что рабочий процесс продвигается вперед, представляет собой набор данных телеметрии, которые вам нужно отобразить, вполне уместно использовать ReportProgress для обновления пользовательского интерфейса с их помощью. Аргумент UserState равен object; нет ограничений на объем информации, которую вы можете передать в пользовательский интерфейс таким образом, если вы определяете класс для ее содержания. - person Robert Rossney; 12.01.2011
comment
@Robert Rossney: справедливо, это правда. Но я все же хотел бы знать, почему использование фонового потока так, как я это сделал, неправильно. - person Dave; 12.01.2011
comment
Если вы обновляете свойства, а затем вызываете ProgressChanged из своего фонового потока, как вы описываете, я очень удивлен, что то, что вы делаете, вообще работает. - person Robert Rossney; 12.01.2011
comment
@Robert Rossney: я не использую BackgroundWorker. Мой фоновый поток обновляет свойство, и мои элементы WPF привязаны к этим свойствам. - person Dave; 12.01.2011
comment
Я это понимаю. Вы используете Invoke для обновления свойства? - person Robert Rossney; 14.01.2011
comment
@ Роберт Россни: а, хорошо. Я вообще не использую Dispatcher для обновления свойства. Я просто устанавливаю значение свойства, и у меня сложилось впечатление, что привязка данных WPF автоматически происходит в основном потоке, а не напрямую через событие OnPropertyChanged. Если я не прав, не могли бы вы опубликовать ссылку на какие-либо вспомогательные статьи? Я работаю таким образом уже более года и еще не видел ни одного сбоя, связанного с потоком, в моем графическом интерфейсе. - person Dave; 14.01.2011
comment
вот еще один ответ на StackOverflow, который поддерживает мое понимание. Конечно, это не означает автоматически, что это правильно. :) stackoverflow. ком/вопросы/3834363/ - person Dave; 14.01.2011