Laden...

ObservableCollection - Add/Remove - Async

Erstellt von CSharpFreak vor 8 Jahren Letzter Beitrag vor 8 Jahren 4.853 Views
C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 8 Jahren
ObservableCollection - Add/Remove - Async

Hallo -

ich habe ein (Verständnis?)-Problem. Und zwar bezieht es sich auf die ObservableCollection.
Ich wollte diese um ein Paar Async-Funktionen erweitern.

z.B. RemoveAsync


        public static Task<Int32> RemoveAsync<T>(this ObservableCollection<T> OCollection, Task<Func<T, Boolean>> Condition)
        {
            return Task.Run<Int32>(async () =>
            {
                List<T> itemsToRemove = OCollection.Where(await Condition).ToList();

                foreach (T itemToRemove in itemsToRemove)
                {
                    OCollection.Remove(itemToRemove);
                }

                return itemsToRemove.Count;
            });
        }


    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public class ObjectItem
        {
            public String Name;
        }

        public ObservableCollection<ObjectItem> DataCollection { get; set; } = new ObservableCollection<ObjectItem>()
        {
            new ObjectItem() { Name = "A" },
            new ObjectItem() { Name = "AB" },
            new ObjectItem() { Name = "ABC" },
            new ObjectItem() { Name = "ABCD" },
            new ObjectItem() { Name = "ABCDE" },
            new ObjectItem() { Name = "ABCDEF" },
        };

        private void button_Click(object sender, RoutedEventArgs e)
        {
            DataCollection.RemoveAsync<ObjectItem>(async (Obj) => 
            {
                await Task.Delay(1000);
                return Obj.Name.Length > 3;
            });
        }
    }

Als Fehlermeldung bekomme ich nun:> Fehlermeldung:

Fehler CS1660 "Lambda-Ausdruck" kann nicht in den Typ "Task<Func<MainWindow.ObjectItem, bool>>" konvertiert werden, da es kein Delegattyp ist.

Was mache ich falsch? Ich würde das ganze gerne so auslegen, dass ich in der "Condition" auch await nutzen kann.

2.080 Beiträge seit 2012
vor 8 Jahren
Func<T, Task<bool>> condition

So müsste der Parameter heißen

Die Methode verlangt an sich ja keinen Task, sondern eine Methode, die intern einen Task erstellt.

Beachte aber: Die ObservableCollection ist meines Wissens nach nicht thread safe

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 8 Jahren

Ja - das habe ich mir so anfangs auch gedacht aber dann...


List<T> itemsToRemove = OCollection.Where(await Condition).ToList();

Besitzt keine Methode mit Func<T, Task<Boolean>> als Parameter, daher dachte ich, dass ich es irgendwie anpassen kann, indem ich eine Task übergebe.

2.080 Beiträge seit 2012
vor 8 Jahren

Das funktioniert nicht, die Where-Methode braucht eine Function, die das Item bekommt und boolean zurück gibt, sie kann nicht mit Tasks umgehen.
Da musst Du dir schon eine WhereAsync-Variante selber bauen.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.842 Beiträge seit 2008
vor 8 Jahren

Die Frage ist: was soll das bringen?
Warum soll die Liste asynchron sein? Aktionen sollen doch asynchron sein...

Sieht für mich nach nem Konzeptionsfehler aus.

D
985 Beiträge seit 2014
vor 8 Jahren

Unabhängig von Sinn oder Unsinn (persönlich halte ich es für Unsinn) müsste die Erweiterung so aussehen:


    public static class Extension
    {
        public async static Task<int> RemoveAsync<T>( this ICollection<T> collection, Func<T, bool> condition )
        {
            return await Task.Run( () =>
            {
                var items = collection.Where( condition ).ToList();
                foreach ( var item in items )
                {
                    collection.Remove( item );
                }
                return items.Count;
            } );
        }
    }

und der Aufruf


        private async void button_Click(object sender, RoutedEventArgs e)
        {
            var count = await DataCollection.RemoveAsync( o => o.Name.Length > 3 );
            // noch was mit count machen?
        }

C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 8 Jahren

Die Frage ist: was soll das bringen?
Warum soll die Liste asynchron sein? Aktionen sollen doch asynchron sein...

Sieht für mich nach nem Konzeptionsfehler aus.

Die Entscheidung, ob ein Object aus der Liste gelöscht wird, hängt von einer Datenbankabfrage (asyncron) ab. Und damit die GUI nicht einfriert wollte ich das so gestallten.

Das kann man sicher noch irgendwie eleganter lösen... jedoch dachte ich für mich, dass ich eine gefunden hätte...
Ich werde die Vorgehensweise noch einmal überdenken.

Unabhängig von Sinn oder Unsinn (persönlich halte ich es für Unsinn) müsste die Erweiterung so aussehen:

  
    public static class Extension  
    {  
        public async static Task<int> RemoveAsync<T>( this ICollection<T> collection, Func<T, bool> condition )  
        {  
            return await Task.Run( () =>  
            {  
                var items = collection.Where( condition ).ToList();  
                foreach ( var item in items )  
                {  
                    collection.Remove( item );  
                }  
                return items.Count;  
            } );  
        }  
    }  
  

und der Aufruf

  
        private async void button_Click(object sender, RoutedEventArgs e)  
        {  
            var count = await DataCollection.RemoveAsync( o => o.Name.Length > 3 );  
            // noch was mit count machen?  
        }  
  

Das Funktioniert an sich aber lässt keine asyncronen Methoden in der Convention zu...

2.080 Beiträge seit 2012
vor 8 Jahren

Die Methode, die Du als Bedingung asynchron haben willst, wird selber in einem Task aufgerufen und läuft daher auch in diesem Task. Sie wird asynchron ausgeführt

Was Du aber erreichen willst, ist gar kein asynchrones RemoveWhere aus einer Liste (das ist Blödsinn), sondern das ganze Aussortieren aus der Liste, vom Aufruf des Commands bis zum Beenden des Selbigen, das muss asynchron ablaufen.

public async Task RemoveWhatEver()
{
    await Task.Factory.StartNew(() => 
    {
        var count = DataCollection.Remove(o => o.Name.Length > 3);
        // das hier findet komplett asynchron statt
    };
}

Beachte aber: Direkter Zugriff auf eine Datenquelle aus einem Task (oder Thread) heraus kann zu Problemen führen. Das Entity Framework kann z.B. kein Multithreading, das wirft sofort eine Exception.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

W
872 Beiträge seit 2005
vor 8 Jahren

Für mich sieht das nach einem sicheren Weg in die Hölle aus.
Wenn die Observable Collection gebunden ist, dann musst Du thread-sicher arbeiten. Mir ist neulich ein Deadlock im WPF passiert, als ich gleichzeitig ein GUI Event und ein asynchrones Event auf einer Observable Collection verarbeitet habe.

16.842 Beiträge seit 2008
vor 8 Jahren

Und damit die GUI nicht einfriert wollte ich das so gestallten.

Dann muss die Aktion selbst asynchron sein, aber nicht die Liste.
Das Prinzip von Async-Verhalten ist wirklich gut - auch insgesamt passend; nur an der falschen Stelle 😃

W
955 Beiträge seit 2010
vor 8 Jahren

Was Du aber erreichen willst, ist gar kein asynchrones RemoveWhere aus einer Liste (das ist Blödsinn), sondern das ganze Aussortieren aus der Liste, vom Aufruf des Commands bis zum Beenden des Selbigen, das muss asynchron ablaufen. Die OberservableCollection<T> muss zwingend im GUI-Thread befüllt oder bearbeitet werden damit sie im richtigen Thread ihre Events feuern kann.

2.080 Beiträge seit 2012
vor 8 Jahren

Das kommt noch dazu, ich hab den Namen DataCollection als Datenbankbasierte Datenquelle interpretiert 😄

@CSharpFreak:

Zusammen mit wittes Hinweis: Sowohl beim Datenzugriff als auch bei der Arbeit in der UI musst Du beim Multithreading aufpassen.

Brauchst Du das asynchrone Verhalten überhaupt? Datenbankabfragen sind langsam, aber ganz sicher nicht so langsam, dass es tatsächlich auffält, dann müssten es schon größere Abfragen sein.
Setze es erst einmal synchron um und schau dann, ob es einen nennenswerten Nachteil gibt und dann kannst Du darüber nachdenken, ob und wie Du es optimierst.
Du könntest von der Datenbank z.B. eine Liste aller IDs (oder Namen) abfragen, die zu dem Item führen, das raus geworfen werden soll. Der Weg von der Anwendung zur Datenbank ist häufig alleine schon langwierig, daher ist es sinnvoll, alles in eine einzelne Abfrage zu legen, die Du dann in einem Rutsch abarbeiten kannst.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
985 Beiträge seit 2014
vor 8 Jahren

Mit ein bisserl guten Willen geht das alles 😁


public static class Extension
    {
        public static async Task<int> RemoveAsync<T>( this ICollection<T> collection, Func<T, Task<bool>> condition )
        {
            // Einträge in eine Liste kopieren
            IList<T> items = collection.ToList();

            // Filtern der Liste in einem Task
            await Task.Run( () =>
                {
                    items = items
                                .AsParallel()
                                .Where( o => condition( o ).Result )
                                .ToList();
                } );

            // Einträge aus der Collection entfernen
            foreach ( var item in items )
            {
                collection.Remove( item );
            }
            return items.Count();
        }
    }

16.842 Beiträge seit 2008
vor 8 Jahren

Fehler, die ich auf den ersten Blick hier seh:
* Race Conditions möglich, da kein Locking stattfindet
* AsParallel() geht nach hinten los, sobald man Tasks kaskadiert - daher nicht unbewusst in ExtensionMethods verwenden

D
985 Beiträge seit 2014
vor 8 Jahren

Du hast recht ... 8o

Sagte ich nicht bereits, dass es eigentlich Unsinn ist 😁