简体   繁体   中英

WPF Multithreading

I am building a WPF application that calls web services and displays the data returned from the service after being broken down and analyzed by my application. The problem that I am facing is with multithreading. One of the API calls is made using a DispatcherTimer every 60 seconds. The issue is that when this event fires, it blocks the UI thread. I have attempted (in all ways that I can think) to update the UI from the background thread using BackgroundWorker and Dispatcher objects (also delegates) and I cannot figure this out. I need an example showing a label on the UI thread being updated by the background thread. Any help with this would be fantastic as I am about to freak out :).

I have looked at the other articles and it is just not making a terrible amount of sense to me. Please, bear with me as I am pretty new to this. Here is an example of what I would like to do. I have a label on the window named lblCase. I call pullData() every 60 seconds and I want to update lblCase with the returned data without blocking the UI.

private void pullData()
{
  //API call goes here...
  lblCase.Content = iCase;
}

public MainWindow()
{
    InitializeComponent();
    DispatcherTimer timer = new DispatcherTimer();
    timer.Tick += new EventHandler(timer_Tick);
    timer.Interval = new TimeSpan(0,0,60);
    timer.Start();
}

private void timer_Tick(object sender, EventArgs e)
{
  pullData();
}

Have a look at this question...

cheers,

EDIT:

Joe - not sure if you're getting any closer to groking this, so I thought I'd try to put together a simple usage of BackgroundWorker to demonstrate how simple and powerful this class is!

first - in your constructor...

public MainWindow()
{
    InitializeComponent();

    BackgroundWork worker = new BackgroundWorker();
    worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

    System.Timers.Timer t = new System.Timers.Timer(10000); //  10 second intervals
    t.Elapsed += (sender, e) =>
    {
        // Don't try to start the work if it's still busy with the previous run...
        if (!worker.IsBusy)
            worker.RunWorkerAsync(); };
    }
}

so we have set up something to delegate some work (in the method 'worker_DoWork') on a background thread... whatever happends in that method will not impact the UI thread, and it should look something like:

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    // Whatever comes back from the lengthy process, we can put into e.Result
    e.Result = DoMyBigOperation();
}

Now when this thread completes, it will fire the RunWorkerCompleted event, which we have handled as such:

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // First, handle the case where an exception was thrown.
    if (e.Error != null)
    {
    // handle the System.Exception
        MessageBox.Show(e.Error.Message);
    }
    else if (e.Cancelled)
    {
        // now handle the case where the operation was cancelled... 
        lblCase.Content = "The operation was cancelled";
    }
    else
    {
        // Finally, handle the case where the operation succeeded
        lblCase.Content = e.Result.ToString();
    }
}

Hope this helps! IanR

You can only update a control from a the thread which created the control. Having said that just use a background worker object to get the data and once that job is complete, update the control using the UI thread.

I prefer to create a worker class, pass it an update delegate, and launch the worker's DoSomething method in a separate thread. Whenever that thread needs to update the UI it calls back to the update delegate, which does the updating through the control's dispatcher. In this example I also pass the control since I have multiple threads each updating their own textblock:

    private void UpdateTextBlock(TextBlock textBlockArg, string textArg)
    {
        textBlockArg.Dispatcher.Invoke(
           System.Windows.Threading.DispatcherPriority.Normal
           , new System.Windows.Threading.DispatcherOperationCallback(delegate
           {
               textBlockArg.Text = textArg;
               return null;
           }), null);
    }

Use BeginInvoke() to update the UI thread from another thread. Here's an article describing how to use it.

Edit: I tried the following program based on the code you gave in your question and it updates the UI fine (because everything is happening on the UI thread).

using System;
using System.Threading;
using System.Windows.Threading;

namespace BeginInvoke
{
    public partial class Window1
    {
        public Window1()
        {
            InitializeComponent();
            x_tid.Text = Thread.CurrentThread.ManagedThreadId.ToString();

            DispatcherTimer timer = new DispatcherTimer();
            timer.Tick += timer_Tick;
            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Start();
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            Thread.SpinWait(900);
            x_text.Text = DateTime.Now.ToString();
            x_tid.Text = Thread.CurrentThread.ManagedThreadId.ToString();
        }
    }
}

.xaml is:

<Window x:Class="BeginInvoke.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBlock x:Name="x_text" />
        <TextBlock x:Name="x_tid" />
    </StackPanel>
</Window>

To elaborate on my original answer about using BeginInvoke to update the UI from a non UI thread, here's a working example. BeginInvoke works off the dispatcher. If you are using Silverlight, you can use the BeginInvoke from System.Windows.Deployment.Current.Dispatcher.BeginInvoke() , instead.

using System;
using System.Threading;
using System.Windows.Threading;

namespace BeginInvoke2
{
    public partial class Window1
    {
        public Window1()
        {
            InitializeComponent();
            ThreadPool.QueueUserWorkItem(Proc, Dispatcher);
        }

        private void Proc(object state)
        {
            var disp = (Dispatcher) state;
            var tid = Thread.CurrentThread.ManagedThreadId.ToString();

            // Use BeginInvoke to do the operations on the UI thread
            disp.BeginInvoke((Action)(() => 
            {
                x_tid1.Text = "threadpool thread: " + Thread.CurrentThread.ManagedThreadId.ToString();
                x_tid2.Text = "        ui thread: " + tid;
            }));

            // Can't do the following operations because we are not in the UI
            // thread
            //x_text.Text = "In Proc";
            //x_tid.Text = Thread.CurrentThread.ManagedThreadId.ToString();
        }
    }
}

.xaml file

<Window x:Class="BeginInvoke2.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBlock x:Name="x_tid1" />
        <TextBlock x:Name="x_tid2" />
    </StackPanel>
</Window>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM