我正在开发一个 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 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 事件被调用(至少函数没有被调用)。
任何人都可以提供一些关于如何调试代码以了解为什么这些函数没有被调用的帮助吗?
另外,上面的代码是答案中推荐的正确方法吗?
所提供的任何帮助将不胜感激。
马特
I am working on a WPF .NET 3.5 application that does a few long tasks that I would like to make a seperate thread to the UI thread to process the data and then when completed update some labels in the UI. The problem I am having is that the function I have uses two parameters and I am struggling to work out how to call a function with multiple parameters in a thread and update the UI.
I have been playing around with using a Delegate Sub to call the function (it is located in a seperate Class), and my code was also attempting to return a dataset from the function for the calling thread to update the UI, but I am not sure if this is the best practice to achieve this or wether I should use a dispatcher for the called function to do the UI updating (feedback would be greatly appreciated).
My code is as follows.
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
And my function that I am attempting to call is as follows:
Class test_global
Public Sub getWeatherData(ByVal filename As String, ByVal location As String) 'As DataSet
...
End Sub
End Class
My problem is if I was to have the calling thread to update the UI, how do I have the called thread to return a dataset, or if the called thread is to update the UI, how do I go about achieving this?
Update:
Following the recomendations provided, I have impletemented a BackgroundWorker that raises a DoWork and RunWorkerCompleted events to get the data and update the UI, respectively. My updated code is as follows:
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
I then have in a seperate class my functions that get and process the data.
I am able to debug the code in Visual Studio 2010 and the form displays but the labels are not updating, and when I put a breakpoint at the RunWorkerAsync line the line is called and the Window_Loaded sub completes but it appears that none of the DoWork or RunWorkerCompleted events are called (well at least the functions are not).
Can anyone provide some assistance on how I can debug the code to see why these functions are not being called?
Also, is the above code the correct method that was recommended in the answers?
Any assistance provided will be greatly appreciated.
Matt
发布评论
评论(3)
您应该使用BackgroundWorker 组件。
您应该在
DoWork
处理程序中调用您的函数,并将e.Result
设置为返回的 DataSet。然后,您可以在 RunWorkerCompleted 处理程序中更新 UI。
You should use the BackgroundWorker component.
You should call your function in the
DoWork
handler and sete.Result
to the returned DataSet.You can then update the UI in the
RunWorkerCompleted
handler.使用
BackgroundWorker
。实现长时间运行的方法,并将参数传递给DoWork
事件处理程序的DoWorkEventArgs
参数中的方法。在此方法中,不要直接或间接更新 UI(即不要更新视图模型的属性)。在方法运行时使用进度报告更新 UI:在长时间运行的方法中调用
ReportProgress
,并在UserState
参数中传递需要显示在 UI 中的任何信息。在ProgressChanged
事件处理程序中,从ProgressChangedEventArgs
获取状态并更新 UI(希望通过更新视图模型的适当属性并引发PropertyChanged )。
您需要实现一个类来包含进度报告的用户状态,因为
UserState
的类型为object
。请注意,您还可以在长时间运行的方法完成后使用其结果更新 UI。这以与进度报告类似的方式完成:实现一个类来包含结果,将
DoWorkEventArgs
的Result
属性设置为此类的实例,然后将结果设置为当引发RunWorkerCompleted
事件时,将在WorkCompletedEventArgs
的Result
属性中提供。通过检查
WorkCompletedEventArgs
的Error
属性,确保处理长时间运行的方法引发的任何异常。Use a
BackgroundWorker
. Implement your long-running method, and pass the arguments to the method in theDoWorkEventArgs
parameters of theDoWork
event handler. Do not update the UI, either directly or indirectly (i.e. don't update properties of your view model), in this method.Use progress reporting to update the UI while the method is running: call
ReportProgress
in the long-running method, passing any information that needs to appear in the UI in theUserState
parameter. In theProgressChanged
event handler, get the state from theProgressChangedEventArgs
and update the UI (by, one hopes, updating the appropriate properties of your view model and raisingPropertyChanged
).You'll need to implement a class to contain the user state for progress reporting, since
UserState
is of typeobject
.Note that you can also update the UI with the results of the long-running method when it's complete. This is done in a similar fashion to progress reporting: implement a class to contain the results, set the
Result
property of theDoWorkEventArgs
to an instance of this class, and the result will be available in theResult
property of theWorkCompletedEventArgs
when theRunWorkerCompleted
event is raised.Make sure that you handle any exceptions that the long-running method raises by checking the
Error
property of theWorkCompletedEventArgs
.我对
BackgroundWorker
没有太多经验(我只使用过一次),但它绝对可以解决您的问题。然而,我总是使用的方法是启动一个新线程(不是通过委托的 ThreadPool 线程),它获取锁,然后更新所有属性。如果您的类实现了 INotifyPropertyChanged,您就可以使用数据绑定让 GUI 在属性更改时自动更新。我用这种方法取得了非常好的结果。至于将调度程序传递给您的线程,我相信您也可以做到。但是,我会谨慎行事,因为我相信我遇到过这样的情况,即我认为我正在使用的调度程序不再与主线程关联。我有一个库需要调用一个涉及 GUI 元素的方法(即使可能不会显示对话框),我通过使用 Dispatcher.Invoke 解决了这个问题。我能够保证我正在使用与主线程关联的调度程序,因为我的应用程序使用 MEF 来导出它。
如果您想了解我发布的任何内容的更多详细信息,请发表评论,我会尽力修饰这些主题。
I don't have much experience with
BackgroundWorker
(I have only used it once), but it is definitely a solution to your problem. However, the approach I always use is to start a new Thread (not ThreadPool thread via delegates), which acquires a lock and then updates all of the properties. Provided that your class implementsINotifyPropertyChanged
, you can then use databinding to have the GUI automatically update any time the property changes. I have had very good results with this approach.As far as passing a Dispatcher to your thread goes, I believe you can do that as well. However, I would tread lightly because I believe I have run into cases with this where the Dispatcher I think I'm using is no longer associated with the main thread. I have a library that needs to call a method that touches GUI elements (even though the dialog might not be displayed), and I solved this problem by using Dispatcher.Invoke. I was able to guarantee that I was using the Dispatcher associated with the main thread because my application uses MEF to Export it.
If you'd like more details about anything I've posted, please comment and I'll do my best to embellish on the topics.