6

I'm very confused by this and it's starting to make me question my whole understanding of the WPF resource system

I have a multi-window application where each Window-derived object runs on a separate thread with separate dispatcher.

Thread t = new Thread(() => {
    Window1 win = new Window1();
    win.Show();
    System.Windows.Threading.Dispatcher.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();

I have a Dictionary1.xaml resource dictionary with a named Style object inside it (it just sets the Background property to Red and is targetted at a TextBox). In my App.xaml I reference Dictionary1.xaml via the ResourceDictionary.MergedDictionaries collection. In the XAML of my other windows I have a StaticResource to the style key in a textbox control, which works.

I'm able to open multiple windows but shouldn't I be getting cross-threading errors? In the constructor of one of the window classes I did this:

Style s = (Style)TryFindResource("TestKey");
Console.WriteLine(((Setter)s.Setters[0]).Property.Name);    // no problem
s.Dispatcher == this.Dispatcher    // false

Since a Style object is derived from DispatcherObject, doesn't that mean it's only accessible to the thread that owns it? And if an object is defined in a ResourceDictionary, doesn't that mean that by default, it's a static instance? How is this even able to work? Why aren't I getting a cross-threading error?

(I erroneously reported a question I since deleted about a cross threading error that was caused by something else)

I'm very confused by this - I thought only frozen Freezable objects were shareable across threads. Why am I allowed to access a DispatcherObject on other threads?

4

3 に答える 3

4

だから私は最終的に答えを持っています-私はたくさんの.NETフレームワークコードを掘り下げて、次の結論に達しました:

リソースディクショナリがアプリケーションレベルのディクショナリ、テーマディクショナリ、または読み取り専用のいずれかである場合、リソースディクショナリに格納されているすべてのアイテムが「封印」されます

if (this.IsThemeDictionary || this._ownerApps != null || this.IsReadOnly)
{
    StyleHelper.SealIfSealable(value);
}

... 

internal static void SealIfSealable(object value)
{
ISealable sealable = value as ISealable;
if (sealable != null && !sealable.IsSealed && sealable.CanSeal)
{
    sealable.Seal();
}
}

オブジェクトを「封印」すると、基本的にオブジェクトが不変になり、ISEalableディクショナリを介して実装されます。実際、Freezableは、Freeze()を呼び出すことで封印動作を実装します。スタイルもそれを実装し、その実装はSettersまたはTriggersコレクションが変更されるのを防ぎます。ISealableは多くのクラスで実装されています!

public abstract class Freezable : DependencyObject, ISealable
public class Style : DispatcherObject, INameScope, IAddChild, ISealable, IHaveResources, IQueryAmbient

さらに重要なことに、これまでに見たすべてのWPFクラスでのSeal()のすべての実装は、ディスパッチャーをnullに設定するDispatcherObject.DetachFromDispatcher()を呼び出します。(Freeze()はこれも内部的に呼び出します)

「シーリング」は、Freezable専用としてアドバタイズされることが多い不変性の動作を実装しているように見えますが、ISealableを実装するオブジェクトは同じ動作を示し、スレッドセーフです-Freezableには、それを区別する追加の動作があります(サブプロパティ通知の変更など)。

つまり、オブジェクトを「封印」すると、事実上、アプリケーションレベルまたはテーマリソースディクショナリまたは読み取り専用リソースに存在する場合、各オブジェクトがディスパッチャから自動的に切り離されるため、スレッド間でオブジェクトを共有できます。辞書

この結論を確認するには、ResourceDictionaryを熟考し、ResourceDictionaryの論理的な親(FrameworkElement、FrameworkContentElement、Applicationなど)を設定するAddOwner()メソッドを確認します。

リソースディクショナリがApplication.Resourcesにマージされ、すべて自動的にシールされたため、ブラシとStyleオブジェクトに他のスレッドからアクセスできるのはこのためです。当然のことながら、これらのリソースをウィンドウレベルに配置すると、通常のクロススレッド例外が発生します。 。

ISealableは、WPFオブジェクトをスレッド間で共有可能にし、読み取り専用にするものであると一般化できると思いますが、技術的にDispatcherから切り離すことはプロトコルの要件ではありません(DispatcherObjectsがすべてのでVerifyAccess()呼び出しを行う必要がないように)プロパティ)独自のSeal()動作を実装するのは技術的にはすべてのオブジェクト次第であり、ISealableとDispatcherの間に直接的な相関関係はないためです。

于 2012-11-26T04:49:04.780 に答える
2

私もあなたの「問題」に非常に混乱しています。ディスパッチャ オブジェクトには魔法はありません。スレッド アフィニティは、DispatcherObject から継承するクラスでコーディングする必要があります。Style.Setters プロパティの getter を定義する方法は次のとおりです。

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SetterBaseCollection Setters
{
    get
    {
        base.VerifyAccess();
        if (this._setters == null)
        {
            this._setters = new SetterBaseCollection();
            if (this._sealed)
            {
                this._setters.Seal();
            }
        }
        return this._setters;
    }
}

したがって、あなたの場合、実際に例外がスローされるはずです...

s.CheckAccess() メソッドを呼び出すこともできます。ディスパッチャー比較 Object.ReferenceEquals(A,B) メソッドを試して、等号演算子がオーバーロードされていないことを確認することもできます (ただし、オーバーロードは見つかりませんでした...)。

このテーマは非常に興味深いものです。お知らせください。

于 2012-11-20T09:17:18.587 に答える
0

いい質問です、私が言及したい2つのポイントがあります、

最初のポイントは、スタイルを要求するたびにスタイルの新しいオブジェクトが提供されるため、スタイルはスタイル情報を含むクラスの一部のようなものであるため、スタイル内のオブジェクトが複数のコントロール間で共有されることはありません。

2番目のポイントは、ディスパッチャーが常に単一のスレッドで実行されているため、ディスパッチャーが例外をスローする必要がある理由です。したがって、コントロールのディスパッチャーを変更すると、スレッド アフィニティ ルールはディスパッチャーを使用し、GUI スレッドでレンダリングします。

この情報は役に立ちますか。

于 2012-11-20T09:47:39.277 に答える