Hvordan opdaterer jeg en ObservableCollection via en arbejdstråd?

Hvordan opdaterer jeg en ObservableCollection via en arbejdstråd?

Ny mulighed for .NET 4.5

Fra .NET 4.5 er der en indbygget mekanisme til automatisk at synkronisere adgang til samlingen og afsendelsen CollectionChanged begivenheder til UI-tråden. For at aktivere denne funktion skal du ringe til BindingOperations.EnableCollectionSynchronization indefra din UI-tråd .

EnableCollectionSynchronization gør to ting:

  1. Husker tråden, hvorfra den kaldes, og får databindingspipelinen til at samle CollectionChanged begivenheder i den tråd.
  2. Anskaffer en lås på samlingen, indtil den organiserede hændelse er blevet håndteret, så hændelseshandlerne, der kører UI-tråd, ikke vil forsøge at læse samlingen, mens den bliver ændret fra en baggrundstråd.

Meget vigtigt er det, dette tager sig ikke af alt :for at sikre trådsikker adgang til en i sagens natur ikke trådsikker samling skal du samarbejde med rammen ved at erhverve den samme lås fra dine baggrundstråde, når samlingen er ved at blive ændret.

Derfor er de nødvendige trin for korrekt drift:

1. Beslut hvilken slags låsning du vil bruge

Dette vil afgøre, hvilken overbelastning af EnableCollectionSynchronization skal bruges. Det meste af tiden en simpel lock statement vil være tilstrækkeligt, så denne overbelastning er standardvalget, men hvis du bruger en eller anden fancy synkroniseringsmekanisme, er der også understøttelse af brugerdefinerede låse.

2. Opret samlingen og aktiver synkronisering

Afhængigt af den valgte låsemekanisme skal du kalde den relevante overbelastning på UI-tråden . Hvis du bruger en standard lock sætningen skal du angive låseobjektet som et argument. Hvis du bruger tilpasset synkronisering, skal du angive en CollectionSynchronizationCallback delegeret og et kontekstobjekt (som kan være null ). Når den påkaldes, skal denne delegerede erhverve din brugerdefinerede lås, påkalde Action gået til den og frigør låsen, før du vender tilbage.

3. Samarbejd ved at låse samlingen, før du ændrer den

Du skal også låse samlingen ved hjælp af den samme mekanisme, når du selv skal ændre den; gør dette med lock() på det samme låseobjekt videregivet til EnableCollectionSynchronization i det simple scenarie, eller med den samme brugerdefinerede synkroniseringsmekanisme i det brugerdefinerede scenarie.


Teknisk set er problemet ikke, at du opdaterer ObservableCollection fra en baggrundstråd. Problemet er, at når du gør det, rejser samlingen sin CollectionChanged-begivenhed på den samme tråd, der forårsagede ændringen - hvilket betyder, at kontrolelementer bliver opdateret fra en baggrundstråd.

For at udfylde en samling fra en baggrundstråd, mens kontroller er bundet til den, skal du sandsynligvis oprette din egen samlingstype fra bunden for at løse dette. Der er dog en enklere mulighed, som måske fungerer for dig.

Send Tilføj opkald til brugergrænsefladetråden.

public static void AddOnUI<T>(this ICollection<T> collection, T item) {
    Action<T> addMethod = collection.Add;
    Application.Current.Dispatcher.BeginInvoke( addMethod, item );
}

...

b_subcollection.AddOnUI(new B());

Denne metode vender tilbage med det samme (før varen rent faktisk føjes til samlingen), og derefter på UI-tråden vil varen blive tilføjet til samlingen, og alle burde være glade.

Virkeligheden er imidlertid, at denne løsning sandsynligvis vil bunde under tung belastning på grund af al den tværgående aktivitet. En mere effektiv løsning ville samle en masse elementer og sende dem til brugergrænsefladetråden med jævne mellemrum, så du ikke ringer på tværs af tråde for hvert element.

BackgroundWorker-klassen implementerer et mønster, der giver dig mulighed for at rapportere fremskridt via dens ReportProgress-metode under en baggrundshandling. Fremskridtene rapporteres på UI-tråden via hændelsen ProgressChanged. Dette kan være en anden mulighed for dig.


Med .NET 4.0 kan du bruge disse one-liners:

.Add

Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));

.Remove

Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));