2012-11-20 4 views
5

Sono molto confuso da questo e sta iniziando a farmi mettere in discussione tutta la mia comprensione del sistema di risorse WPFPerché Freezables è già stato congelato e ha un Dispatcher nullo (uguale agli Stili) quando è archiviato in Application.Resources?

Ho un'applicazione multi-finestra in cui ogni oggetto derivato da Windows viene eseguito su un thread separato con dispatcher separato.

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

Ho un dizionario risorse Dictionary1.xaml con un oggetto Style chiamato al suo interno (imposta solo la proprietà di sfondo di rosso ed è mirato ad una TextBox). Nella mia app.xaml faccio riferimento a Dictionary1.xaml tramite la raccolta ResourceDictionary.MergedDictionaries. Nella XAML delle altre finestre ho una risorsa statica per la chiave di stile in un controllo casella di testo, che funziona.

Sono in grado di aprire più finestre, ma non dovrei ricevere errori di cross-threading? Nel costruttore di una delle classi finestra ho fatto questo:

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

Dal momento che un oggetto Style è derivato da DispatcherObject, non significa che è accessibile solo al thread che lo possiede? E se un oggetto è definito in un ResourceDictionary, non significa che per impostazione predefinita è un'istanza statica? Come è anche in grado di funzionare? Perché non ricevo un errore di cross-threading?

(ho erroneamente riportato una domanda che in quanto cancellato su un errore di threading croce che è stato causato da qualcos'altro)

Sono molto confuso da questo - ho pensato solo oggetti Freezable congelati erano condivisibili attraverso i thread. Perché sono autorizzato ad accedere a DispatcherObject su altri thread?

risposta

4

Così ho finalmente una risposta - ho scavato attraverso un sacco di codice .NET framework e giunto alle seguenti conclusioni:

Quando un dizionario risorse è o un dizionario a livello di applicazione, un dizionario di tema o di sola lettura solo, tutti gli elementi memorizzati nel dizionario risorse saranno "sigillate"

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(); 
} 
} 

"tenuta" un oggetto rende essenzialmente immutabile, è attuata mediante il dizionario ISealable - infatti congelabile implementa è comportamento di tenuta chiamando congelamento() ! Anche gli stili lo implementano e la sua implementazione impedisce la modifica delle collezioni Setters o Trigger. ISealable è implementato da molte classi!

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

Ancora più importante, ogni implementazione di Seal() in ogni classe WPF che ho visto (finora) chiama DispatcherObject.DetachFromDispatcher(), che stabilisce il dispatcher su null! (Freeze() chiama internamente anche questo)

"Sealing" sembra implementare il comportamento di immutabilità che è spesso pubblicizzato come esclusivo di Freezable, ma gli oggetti che implementano ISealable mostreranno lo stesso comportamento e saranno thread-safe - Freezables ha ulteriori comportamento che lo distingue (es. modifica della notifica di sub-proprietà)

Quindi, per concludere, "sigillare" un oggetto, in effetti, consente di condividerlo tra i thread perché ogni oggetto viene staccato automaticamente dal proprio dispatcher se è presente in un dizionario delle risorse a livello di applicazione o tema o in un dizionario delle risorse di sola lettura

Per verificare questa conclusione, riflettere o ver ResourceDictionary e guarda il metodo AddOwner() che imposta il genitore logico del ResourceDictionary (es. un FrameworkElement, FrameworkContentElement o Application)

Ecco perché i pennelli e l'oggetto Stile erano accessibili da altri thread, perché i dizionari delle risorse erano uniti in Application.Resources e sono stati quindi tutti automaticamente sigillati - non sorprendentemente mettendo queste risorse a livello di finestra causerà la solita eccezione di cross-threading.

Immagino che è possibile generalizzare che ISealable è ciò che consente agli oggetti WPF di essere condivisibili tra thread e di sola lettura, anche se la distacco tecnico dal Dispatcher non è un requisito del protocollo (proprio come DispatcherObjects non è richiesto per rendere VerifyAccess (chiama in ogni proprietà) poiché è tecnicamente valido per ogni oggetto implementare il proprio comportamento Seal() e non vi è alcuna correlazione immediata tra ISealable e Dispatcher

2

Sono anche molto confuso con il tuo "problema". Non c'è magia con l'oggetto dispatcher, l'affinità del thread dovrebbe essere codificata nella classe che eredita da DispatcherObject: Ogni accessor che non dovrebbe essere compatibile con il multi thread dovrebbe chiamare il metodo base.VerifyAccess() fornito da DispatcherObject. E qui è il modo in cui il getter della proprietà Style.Setters è definito:

[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; 
    } 
} 

Quindi nel tuo caso, un'eccezione dovrebbe effettivamente stata gettata ...

Forse si potrebbe provare a chiamare il s.CheckAccess() metodo. Si potrebbe anche provare il metodo Object.ReferenceEquals (A, B) per il confronto di dispatcher per garantire il fatto che l'operatore uguale non sia stato sovraccaricato (sebbene non abbia rilevato alcun sovraccarico ...).

Questo argomento è molto interessante, tienici informati!

+0

Sì, ho provato CheckAccess e è tornato true mentre VerifyAccess non ha generato un'eccezione . ReferenceEquals restituisce anche false prevedibilmente. Grazie per avermi ricordato che DispatcherObject non è un oggetto magico e che l'affinità del thread viene applicata da chiamate manuali a VerifyAccess() - ottimo punto/promemoria! Molto confuso ancora – blue18hutthutt

+1

Come ulteriore test, ho appena creato un nel dizionario delle risorse. Questo dovrebbe creare un nuovo SolidColorBrush non congelato, che per definizione NON dovrebbe essere accessibile su un altro thread e tuttavia ... è AND quando lo richiamo da un'altra finestra - è già congelato! La mia comprensione di come pensavo che WPF funzionasse è semplicemente crollata ... – blue18hutthutt

+1

Ecco il codice per il metodo Dispatcher.CheckAccess(): "return this.Thread == Thread.CurrentThread;" "this.Thread" è naturalmente l'istanza del thread memorizzata nell'istanza dell'oggetto. Potresti confermare che questo test restituisce anche per te ???? –

0

Nizza domanda, ci sono due punti che vorrei parlare,

primo punto è, ogni volta che si richiede uno stile che vi darà un nuovo oggetto di stile così mai gli oggetti in uno stile sono condivisi tra più controlli perché lo stile è come una parte della classe che contiene informazioni di stile.

Secondo punto, perché il dispatcher deve generare un'eccezione poiché il dispatcher è sempre in esecuzione su un singolo thread. Pertanto, quando si modifica il dispatcher di un controllo, la regola di affinità del thread utilizza il dispatcher e visualizza le cose sul thread della GUI.

Fa questa informazione di aiuto.

+1

Questo non è corretto - qualsiasi oggetto istanziato in una raccolta di risorse è di default condiviso. L'ho verificato solo aggiungendo lo stile a un oggetto cache globale all'inizio, quindi facendo un confronto ReferenceEquals dal costruttore di una delle classi della finestra figlio e sono uguali (x: Shared = "false" per avere istanze separate). Penso che dovrebbe essere generata un'eccezione perché ogni finestra viene eseguita su un thread separato e sto anche avviando un dispatcher separato su ciascun thread, quindi l'oggetto Style dovrebbe essere inaccessibile senza il marshalling – blue18hutthutt

+0

dipende dall'uso ma lo stile è sempre come questo .. puoi usare uno stile in molte risorse. e se uno stile che influenza il cambiamento non significa che cambierà in tutti i punti in cui viene applicato. se questo non è il tuo caso, potrebbe essere diverso. –

+0

Bene, tutto dipende da dove è definito lo stile - se è memorizzato in Window.Resources per ciascuna delle mie classi di finestre personalizzate, allora sarebbe un oggetto Style separato per ogni istanza di finestra, tuttavia se sono definite in App.xaml esse sarà lo stesso oggetto Style – blue18hutthutt

Problemi correlati