【问题标题】:Threaded Function has multiple parameters and returns data线程函数有多个参数并返回数据
【发布时间】:2011-01-12 13:58:37
【问题描述】:

我正在开发一个 WPF .NET 3.5 应用程序,该应用程序执行一些长任务,我想为 UI 线程创建一个单独的线程来处理数据,然后在完成后更新 UI 中的一些标签。我遇到的问题是我的函数使用了两个参数,我正在努力研究如何在线程中调用具有多个参数的函数并更新 UI。

我一直在尝试使用 Delegate Sub 来调用函数(它位于单独的类中),并且我的代码还尝试从函数返回数据集以供调用线程更新 UI,但是我不确定这是否是实现这一目标的最佳实践,或者我是否应该为被调用函数使用调度程序来进行 UI 更新(非常感谢反馈)。

我的代码如下。

    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

我的问题是,如果我要让调用线程更新 UI,我如何让被调用线程返回数据集,或者如果被调用线程要更新 UI,我该如何实现呢?

更新:

根据提供的建议,我实现了一个 BackgroundWorker,它引发 DoWork 和 RunWorkerCompleted 事件以分别获取数据和更新 UI。我的更新代码如下:

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 事件(至少函数没有)。

谁能提供一些帮助,帮助我调试代码,看看为什么这些函数没有被调用?

另外,上面的代码是答案中推荐的正确方法吗?

我们将不胜感激。

马特

【问题讨论】:

  • 我已经更新了问题,我已经按照建议实现了 BackgroundWorker,但是我在获取这两个函数来处理 DoWork 和 RunWorkerCompleted 事件时遇到了问题。

标签: wpf multithreading .net-3.5 dispatcher


【解决方案1】:

您应该使用BackgroundWorker component

您应该在DoWork 处理程序中调用您的函数并将e.Result 设置为返回的DataSet。
然后,您可以在 RunWorkerCompleted 处理程序中更新 UI。

【讨论】:

    【解决方案2】:

    使用BackgroundWorker。实现长时间运行的方法,并将参数传递给DoWork 事件处理程序的DoWorkEventArgs 参数中的方法。不要在此方法中直接或间接更新 UI(即不要更新视图模型的属性)。

    在方法运行时使用进度报告更新 UI:在长时间运行的方法中调用 ReportProgress,在 UserState 参数中传递任何需要出现在 UI 中的信息。在ProgressChanged 事件处理程序中,从ProgressChangedEventArgs 获取状态并更新UI(希望通过更新视图模型的适当属性并引发PropertyChanged)。

    您需要实现一个类来包含用于进度报告的用户状态,因为UserState 的类型为object

    请注意,您还可以在长时间运行的方法完成后使用其结果更新 UI。这以与进度报告类似的方式完成:实现一个包含结果的类,将DoWorkEventArgsResult 属性设置为此类的一个实例,结果将在Result 属性中可用引发RunWorkerCompleted 事件时的WorkCompletedEventArgs

    确保通过检查WorkCompletedEventArgsError 属性来处理长时间运行的方法引发的任何异常。

    【讨论】:

    • 请看我更新的代码,你讨论的PropertyChanged和INotifyPropertyChanged一样吗?
    • INotifyPropertyChanged 是接口。 PropertyChanged 是事件。
    • 我已将此标记为答案,因为您提供了大量信息并将我指向 PropertyChanged 事件。非常感谢您的帮助。
    【解决方案3】:

    我对@9​​87654322@没有太多经验(我只用过一次),但它绝对可以解决你的问题。但是,我总是使用的方法是启动一个新线程(不是通过委托的 ThreadPool 线程),它获取一个锁,然后更新所有属性。如果您的类实现了INotifyPropertyChanged,那么您可以使用数据绑定让GUI 在属性更改时自动更新。我用这种方法取得了很好的效果。

    就将 Dispatcher 传递给您的线程而言,我相信您也可以这样做。但是,我会轻描淡写,因为我相信我遇到过这样的情况,我认为我正在使用的 Dispatcher 不再与主线程相关联。我有一个库需要调用一个接触 GUI 元素的方法(即使可能不显示对话框),我通过使用 Dispatcher.Invoke 解决了这个问题。我能够保证我正在使用与主线程关联的 Dispatcher,因为我的应用程序使用 MEF 来导出它。

    如果您想了解有关我发布的任何内容的更多详细信息,请发表评论,我会尽力修饰这些主题。

    【讨论】:

    • 那行不通;您将从背景线程更新 UI 对象。此外,您应该使用 ThreadPool。
    • 您可以在使用BackgroundWorker 时通过其进度报告功能更新 UI 对象。很少需要在 WPF 应用程序中显式使用 System.Threading
    • @SLaks 你的意思是它不能很好地工作,就像有一种更有效的方法,还是你的意思是这种方法完全有缺陷并且根本不起作用?如果是后者,那么我很困惑,因为我总是有后台线程更新属性,然后只是从主 GUI 数据绑定到这些属性。没有任何线程问题(即从非主线程更新)。为什么它必须是线程池?如果这是一个长时间运行的进程,那么后台线程应该是正确的方法。
    • 报告进度不只是更新完成百分比的进度条。如果您的工作人员正在取得进展的迹象是您需要显示的一堆遥测数据,那么使用ReportProgress 来更新 UI 是非常合适的。 UserState 参数是 object;如果您定义一个类来包含它,那么您可以通过这种方式传递给 UI 的信息量没有限制。
    • 这是 StackOverflow 上支持我理解的另一个答案。当然,这并不意味着它是正确的。 :) stackoverflow.com/questions/3834363/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-24
    • 1970-01-01
    • 2015-07-23
    • 2021-12-21
    相关资源
    最近更新 更多