0

I have a program that has stock quotes pushed to me via an API. The program also has a front end, made in XAML, that freezes while this program is running (i.e. processing the information that the API is sending me). I've tried using Dispatcher.Invoke and/or BackgroundWorker and have read up on threading plenty, but can't get it to unfreeze. Perhaps I'm just doing something wrong. I've attached the relevant code here. Was hoping someone could help.

    private void QuoteUpdate(QuoteInfo info)
    {
        BackgroundWorker bwQuoteUpdate = new BackgroundWorker();
        bwQuoteUpdate = new BackgroundWorker();
        bwQuoteUpdate.WorkerSupportsCancellation = true;

        bwQuoteUpdate.DoWork += bwQuoteUpdate_DoWork;            
        bwQuoteUpdate.RunWorkerAsync(info);         
    }

    private void bwQuoteUpdate_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(() =>
            {
                QuoteInfo info = e.Argument as QuoteInfo;
                //logical functions and work are here

            }));
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show("Error in QuoteUpdate: " + ex.Message, "Exception Thrown");
        }

    }        
jeev7882
  • 1
  • 1

3 Answers3

1

Although you’re creating a BackgroundWorker with the intention of executing your long-running task on a background thread, you’re still dispatching all your processing back onto the UI thread.

private void bwQuoteUpdate_DoWork(object sender, DoWorkEventArgs e)
{
    // Code here runs on background thread.

    Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(() =>
    {
        // Code here runs on UI thread.
    }));
}

What you need to do is first perform your calculations on the background thread, but do not update any UI components; rather, store all your results in local variables. Then, once that’s done, use the Dispatcher.Invoke to dispatch control back to the UI thread, and use the results stored in your local variables to update your UI.

For example:

private void bwQuoteUpdate_DoWork(object sender, DoWorkEventArgs e)
{
    // Code here runs on background thread.
    QuoteInfo info = e.Argument as QuoteInfo;
    string result = PerformLongRunningProcessing(info);

    Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(() =>
    {
        // Code here runs on UI thread.
        this.resultTextBox.Text = result;
    }));
}
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • Excellent! Thanks for this answer Douglas. That makes complete sense. I didn't realize that Dispatcher.Invoke was dispatching everything back to the UI thread. I'll have to do some work to separate out any UI updates from the functions, but that seems to be the logical solution. – jeev7882 May 08 '12 at 18:24
  • Yes, you’ll need to perform that separation – unless you want to scatter `Dispatcher.Invoke` calls _inside_ your functions wherever you need to do UI updates. (Usually not a good design, but depends on your scenario really.) Another alternative to using `Dispatcher.Invoke` is to subscribe to the `RunWorkerCompleted` event of your `BackgroundWorker` instance, which would always be raised on the UI thread (assuming the `BackgroundWorker` is initialized on the UI thread too). – Douglas May 08 '12 at 18:30
  • Could you briefly explain why using Dispatcher.Invoke calls inside functions is not a good design? Is it slower and/or less robust? – jeev7882 May 08 '12 at 18:35
  • Mostly because it’s slower, since it involves thread synchronization (between the background thread and the UI thread), interrupting the background processing each time. (`BeginInvoke` allows you to be asynchronous, but you need to be more careful with race conditions then.) However, like I said, it depends on your scenario. For example, if you need to periodically (say, every 250 ms) show progress updates on your UI, then it’s acceptable to use `Dispatcher.Invoke` for that end. – Douglas May 08 '12 at 18:41
  • Speed is something I can't sacrifice. I'll will put in the work to split out UI updates from my functions. I could get up to 5K quotes per second, so dealing with race conditions is something I'd rather avoid. I think BeginInvoke would work about 99% of the time, but 1% failure in this case is very significant. Thanks again Douglas, you've been a huge help. – jeev7882 May 08 '12 at 18:48
1

Yes, you are doing something wrong. The computation should be done in thread alone add only UI changes should be done in Dispatcher.Invoke.

And if you use DataBinding through INotifyPropertyChange, then drop the Dispatcher.Invoke completly, because marshaling the changes to UI thread is done automaticaly.

Euphoric
  • 12,645
  • 1
  • 30
  • 44
  • 1
    Are you sure about the last part? If you’re data-binding to a dependency property, then attempting to update the dependency property from a background thread would give you an exception. – Douglas May 08 '12 at 18:07
  • I'm talking about INotifyPropertyChanged. And only lunatic would use DependencyProperty in (view)model. This being one of the reasons. – Euphoric May 08 '12 at 18:08
  • You’re mistaken. If a UI component binds to a property which has a `PropertyChanged` event raised from a background thread, you will get an `InvalidOperationException` ("Cross-thread operation detected.") – Douglas May 08 '12 at 18:16
  • Yes, WPF does that out-of-box. But it doesn't work for collections. More : http://stackoverflow.com/questions/1321423/does-wpf-databinding-marshall-changes-to-the-ui-thread – Euphoric May 08 '12 at 18:30
  • I think you should clarify that in your answer, as well as specify that it only applies to .NET 3.5 and above (not .NET 3.0). – Douglas May 08 '12 at 18:36
  • I am sure this didn’t work on .NET 3.0 (and there are a number of blog posts to corroborate, such as [INotifyPropertyChanged and Cross-thread exceptions](http://www.claassen.net/geek/blog/2007/07/inotifypropertychanged-and-cross-thread-exceptions.html)); however, I can’t reproduce it now. Possibly Microsoft fixed it through a patch for .NET 3.0 as well. – Douglas May 08 '12 at 19:07
0

Try

Dispatcher.BeginInvoke(...)
Chris
  • 26
  • 1
  • As Douglas already stated, this can cause issues with race conditions. With the frequency of events I'm dealing with, this would be a huge pain to implement. – jeev7882 May 09 '12 at 13:17