What solutions do I have if I want to prevent the UI from freezing while I deserialize a large number of UI elements in WPF? I'm getting errors complainig that the objects belong on the UI Thread when I'm trying to load them in another thread. So, what options do I have to prevent the Vista "Program not responding" error while I'm loading my UI data? Can I rely on a single-threaded solution, or am I missing something regarding perhaps multiple UI Threads?
If you only use a single thread then the UI will freeze while you do any amount of processing.
If you use a BackgroundWorker thread you'll have more control over what happens & when.
To update the UI you need to use Dispatcher.Invoke
from your background thread to marshal the call across the thread boundary.
Dispatcher.Invoke(DispatcherPriority.Background,
new Action(() => this.TextBlock.Text = "Processing");
You can turn the flow of control on its head using DispatcherFrames, allowing a deserialization to proceed on the UI thread in the background.
First you need a way to get control periodically during deserialization. No matter what deserializer you are using, it will have to call property sets on your objects, so you can usually add code to the property setters. Alternatively you could modify the deserializer. In any case, make sure your code is called frequently enough
Each time you receive control, all you need to do is:
In addition, when calling the deserializer itself make sure you do it from Dispatcher.BeginInvoke, or that your calling code doesn't hold any locks etc.
Here's how it would look:
public partial class MyWindow
{
SomeDeserializer _deserializer = new SomeDeserializer();
byte[] _sourceData;
object _deserializedObject;
...
void LoadButton_Click(...)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
_deserializedObject = _deserializer.DeserializeObject(_sourceData);
}));
}
}
public class OneOfTheObjectsBeingDeserializedFrequently
{
...
public string SomePropertyThatIsFrequentlySet
{
get { ... }
set { ...; BackgroundThreadingSolution.DoEvents(); }
}
}
public class BackgroundThreadingSolution
{
[ThreadLocal]
static DateTime _nextDispatchTime;
public static void DoEvents()
{
// Limit dispatcher queue running to once every 200ms
var now = DateTime.Now;
if(now < _nextDispatchTime) return;
_nextDispatchTime = now.AddMilliseconds(200);
// Run the dispatcher for everything over background priority
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
frame.Continue = false;
}));
Dispatcher.PushFrame(frame);
}
}
Checking DateTime.Now in DoEvents() isn't actually required for this technique to work, but will improve performance if SomeProperty is set very frequently during deserialization.
Edit: Right after I wrote this I realized there is an easier way to implement the DoEvents method. Instead of using DispatcherFrame, simply use Dispatcher.Invoke with an empty action:
public static void DoEvents()
{
// Limit dispatcher queue running to once every 200ms
var now = DateTime.Now;
if(now < _nextDispatchTime) return;
_nextDispatchTime = now.AddMilliseconds(200);
// Run the dispatcher for everything over background priority
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(() => {}));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With