0

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

Lima
  • 1,203
  • 3
  • 22
  • 51
  • I have updated the question, I have implemented the BackgroundWorker as recommended, however I am having problems getting the two functions to handle the DoWork and RunWorkerCompleted events. – Lima Jan 13 '11 at 11:47

3 Answers3

2

You should use the BackgroundWorker component.

You should call your function in the DoWork handler and set e.Result to the returned DataSet.
You can then update the UI in the RunWorkerCompleted handler.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
1

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 implements INotifyPropertyChanged, 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.

Dave
  • 14,618
  • 13
  • 91
  • 145
  • That won't work well; you'll be updating UI objects from a bckground thread. Also, you should use the ThreadPool. – SLaks Jan 12 '11 at 17:17
  • You can update UI objects when you're using a `BackgroundWorker` by using its progress-reporting functionality. It's rarely necessary to explicitly use `System.Threading` in a WPF application. – Robert Rossney Jan 12 '11 at 18:06
  • @SLaks Do you mean it won't work well, like there is a more efficient way, or do you mean that the approach is completely flawed and won't work at all? If the latter, then I am confused because I always have background threads update properties, and then simply databind to these properties from the main GUI. Haven't had any threading issues (i.e. update from non-main thread). Why does it have to be the ThreadPool? If it's a long running process, then a background thread is supposedly the proper way to do it. – Dave Jan 12 '11 at 18:07
  • @Robert Rossney: the only reason why I don't use BackgroundWorker is that what I do in threads isn't for progress reasons. It's usually related to getting telemetry data and constantly displaying it in a window. – Dave Jan 12 '11 at 18:09
  • 1
    Reporting progress isn't just updating a progress bar with a percent completed. If your indication that the worker is making progress is a bunch of telemetry data that you need to display, it's perfectly appropriate to use `ReportProgress` to update the UI with it. The `UserState` argument's an `object`; there's no limit on the amount of information you can pass to the UI that way if you define a class to contain it. – Robert Rossney Jan 12 '11 at 18:26
  • @Robert Rossney: fair enough, that is true. But I'd still like to know why using a background thread the way I have done it is wrong. – Dave Jan 12 '11 at 18:57
  • If you're updating properties and then raising `ProgressChanged` from your background thread, as you describe, I'm very surprised that what you're doing works at all. – Robert Rossney Jan 12 '11 at 19:02
  • @Robert Rossney: I'm not using `BackgroundWorker`. My background thread updates a property, and my WPF elements are databound to these properties. – Dave Jan 12 '11 at 20:27
  • I understand that. Are you using `Invoke` to update the property? – Robert Rossney Jan 14 '11 at 01:41
  • @Robert Rossney: ah, ok. I am not using the Dispatcher at all to update the property. I am merely setting the property value and was under the impression that WPF databinding automatically occurs on the main thread, and not directly via the OnPropertyChanged event. If I am incorrect, can you please post a link to any supporting articles? I've been running this way for over a year and have yet to see a single thread-related crash in my GUI. – Dave Jan 14 '11 at 03:51
  • 1
    here is another answer on StackOverflow that supports my understanding. Of course that doesn't automatically mean it's correct. :) http://stackoverflow.com/questions/3834363/wpf-mvvm-updating-ui-bound-properties-from-backgroundworker – Dave Jan 14 '11 at 03:53
1

Use a BackgroundWorker. Implement your long-running method, and pass the arguments to the method in the DoWorkEventArgs parameters of the DoWork 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 the UserState parameter. In the ProgressChanged event handler, get the state from the ProgressChangedEventArgs and update the UI (by, one hopes, updating the appropriate properties of your view model and raising PropertyChanged).

You'll need to implement a class to contain the user state for progress reporting, since UserState is of type object.

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 the DoWorkEventArgs to an instance of this class, and the result will be available in the Result property of the WorkCompletedEventArgs when the RunWorkerCompleted event is raised.

Make sure that you handle any exceptions that the long-running method raises by checking the Error property of the WorkCompletedEventArgs.

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • Please see my updated code, is the PropertyChanged that you discussed the same as INotifyPropertyChanged? – Lima Jan 13 '11 at 11:49
  • 1
    `INotifyPropertyChanged` is the interface. `PropertyChanged` is the event. – Robert Rossney Jan 14 '11 at 01:41
  • I have marked this as the answer as you provided loads of information and pointed me to the PropertyChanged event. Thank you very much for your assistance. – Lima Jan 14 '11 at 14:34