问题
I have came across a problem while developing a WPF application. The application is based on Prism. The application boots using the prism bootstraper and before loading any Window, the app opens a modal dialog on a different thread (STA), and then a bunch of stuff are loaded (services and etc.) The dialog is open during that time and allows to notify the user on the progress of the application start-up process (using event aggregator to pass the updates). When loading is done, bootstraper closes the dialog and opens the main application window. So far so good... Then when closing the application, same thing is going on. the Main window is closed, a dialog box is opened (again on a new STA thread), to allow notifications. But now, when hitting the ShowDialog call (which occurs inside the new STA thread), an exception is raised: "Cannot use a DependencyObject that belongs to a different thread than its parent Freezable". After long long hours of debugging I have figured out the cause for the exception was the background of the window which is a brush/image taken from a merged dictionary at the application level (instantiated on the wpf UI thread). If loading the image without a ResouceDictionary - everything goes well.
To summaries: The exception is observed only when using a resourceDictionary and only on the second call to a new STA thread which in turn loads up a DialogBox and raise an exception exactly when calling ShowDialog If you have only one dialog (for example no dialog at boot time and only dialog at the shutdown process), then the exception will not occur.
My question is then: what is the reason for that? what exactly this exception mean in this case? (I understand that in general there is some kind of UI thread updates form other threads, but then I do not understand why this happens only on the second instance of the dialgo+thread).
Thanks :)
回答1:
As you correctly mentioned, your background object was created on main UI thread. Your background is actually a Brush object and Brush is a DependencyObject.
When a DependencyObject is created it "depends" on the STA thread it was created on. So like other dependency objects it can only be used on its own thread. This means that STA and kind of compatibility to old COM objects model.
So when you try use it on the other STA thread you get an appropriate exception.
P.S I have the same problem with images that have been defined as resources.
回答2:
I had a similar issue with this. I'm not sure how are you are implementing the background. I can try to explain my situation and maybe you can get something out of it. I created my own base Window let's call it MyWindow which is inheriting from Window. Ie.
public class MyWindow : Window
{
}
What I was going for is applying the background from a dynamic resource from the applications resource dictionary.
I first went for this answer
public class MyWindow : Window
{
public MyWindow()
{
this.SetResourceReference(BackgroundProperty, "MyResourceKey");
}
}
Now this worked for me when the Resource was a set color ie.
<SolidColorBrush x:Key="MyResourceKey" Color="White"/>
I found that when I set the resource reference to a system color I would get the issue you are getting.
<SolidColorBrush x:Key="MyResourceKey" Color="{DynamicResource {x:Static SystemColors.WindowColorKey}}"/>
It would work the first time but the 2nd time I'd get the parent freezable error. So my initial thought was, oh it's a threading issue I just need to invoke the dispatcher. Now this is where I got in a snag because I thought I needed to invoke it on the window. Not true. You need to invoke it on the dependency object of that resource.
The problem. You don't have the object using SetResourceReference because it looks for the resource and creates a reference to it. So what we need is the actual dependency object. To get the object from the resource you can do this.
object temp = this.TryFindResource("MyResourceKey");
Now you have the object but this needs to be a dependency object. I haven't tried this but you may just be able to do this
DependencyObject temp = (DependencyObjet)this.TryFindResource("MyResourceKey");
Now you have the dependency object! This is what is causing our threading issue with a freezable parent. Now we invoke the dispatcher on this object. I eventually ended up with something like this. This worked for me but I might try to clean it up a bit.
public class MyWindow: Window
{
public MyWindow()
{
SetResources();
}
private void SetResources()
{
DependencyObject dependencyObject;
object temp;
temp = this.TryFindResource("MyResourceKey");
if (temp != null)
{
if (temp is DependencyObject)
{
dependencyObject = (DependencyObject)temp;
if (!dependencyObject.CheckAccess())
{
dependencyObject.Dispatcher.BeginInvoke(new System.Action(() => { this.SetResources(); }));
}
else
{
this.SetValue(BackgroundProperty, temp);
}
}
}
}
}
Now this is just setting the background property. I believe this should work the same for a style. So you can do
this.SetValue(StyleProperty, temp)
Took a little bit to figure it out. But once I got it working I was exhilarated. It looks like the dependency object our resource is using is encountering a threading issue hence it's loading the first time but not the 2nd. The first time it's on the correct thread, but somewhere along the way another thread is set off. Yet to figure this one out. If someone has a better solution to this I would love to see it.
来源:https://stackoverflow.com/questions/12905486/cannot-use-a-dependencyobject-that-belongs-to-a-different-thread-than-its-parent