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.
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.
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.
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.
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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?
}
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...
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.
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.
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 😃
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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.
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.
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();
}
}
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
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Du hast recht ... 8o
Sagte ich nicht bereits, dass es eigentlich Unsinn ist 😁