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 = 开发者_如何学Cnew 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>
精彩评论