Hallo allerseits,
in einem ViewModel möchte ich die Möglichkeit haben, bestimmte Eigenschaften asynchron zu aktualisieren.
Hier ein Beispiel eines ViewModels für ein DataGrid (der Code ist aufs Wesentliche reduziert). Die Methode RefreshAsync aktualisiert die Liste der Elemente, welche mittels Binding im DataGrid angezeigt werden.
namespace MyApp.ViewModel
{
public abstract class ListViewModelBase<T> : ViewModelBase
{
public RangeEnabledObservableCollection<T> ItemList { get; protected set; }
public ListViewModelBase()
{
ItemList = new RangeEnabledObservableCollection<T>();
}
public async virtual Task RefreshAsync()
{
IEnumerable<T> items = await Task.Run(() => GetItems());
ItemList.Replace(items);
}
public abstract IEnumerable<T> GetItems();
}
}
Jetzt frage ich mich: ich könnte die Zeile
IEnumerable<T> items = await Task.Run(() => GetItems());
durch diese ersetzen:
IEnumerable<T> items = await Application.Current.Dispatcher.InvokeAsync(() => GetItems());
Meine Frage: wäre es hier besser, statt Task.Run() den Dispatcher-Thread zu verwenden, da ja hier letztlich die GUI aktualisiert wird?
Gruß
Hallo sugar76,
kommt darauf an wie aufwändig GetItems
ist. Ist es nur wenig aufwändig, so kannst du den Dispatcher dafür verwenden. Ist es jedoch aufwändiger -- z.B. dauert länger als 1s --, so würde ich die Arbeit lieber im ThreadPool erledigen lassen, da so der Dispatcher für UI-Aufgaben (rendern, ...) frei bleibt.
Wenn GetItems
auf eine Datenbank od. einen WebService zugreift, so kannst du dort das async
"durchziehen", dann wird der ThreadPool auch nicht wirklich belastet. Achte bei diesen Aufrufen aber dass ConfigureAwait(false)
angegeben wird, denn hier ist es nicht nötig zurück in den UI-Thread zu delegieren. Das ist nur beim letzten await
nötig, von dem aus es dann zur UI geht (wie in deinem Code).
mfG Gü
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.
"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"
Super, besten Dank für die Erklärung. Der Dispatcher sollte also nur für Aufgaben verwendet werden, welche unmittelbar die GUI betreffen und nicht für sonstige Aufgaben (Daten aus der Datenbank laden, etc.).
Da GetItems()
abstrakt und somit die Dauer unbekannt ist, werde ich weiterhin das Task.Run()
verwenden.
Gruß
gfoidl meint wenn die aufzurufende Klasse eine asynchrone Schnittstelle aufweist kannst du diese mit await konsumieren und musst nicht Task.Run verwenden.
Der Dispatcher sollte also nur für Aufgaben verwendet werden, welche unmittelbar die GUI betreffen und nicht für sonstige Aufgaben
gfoidl meint wenn die aufzurufende Klasse eine asynchrone Schnittstelle aufweist kannst du diese mit await konsumieren und musst nicht Task.Run verwenden.
Dann müsste man aber GetItems selbst als asynchron definieren oder?
public abstract Task<IEnumerable<T>> GetItemsAsync();
Sei vorsichtig mit diesen beiden Aufrufen.
Dispatcher.InvokeAsync führt Aufgaben im UI-Thread durch.
Task.Run führt Aufgaben im Thread vom Thread-Pool aus, nicht im UI-Thread.
Eine Aufgabe, die im UI-Thread ausgeführt wird, blockiert Diesen so lange, bis die Aufgabe abgeschlossen ist.
Wenn eine Aufgabe, die in einem anderen Thread ausgeführt wird, auf Daten zugreift, die von einem anderen Thread gelesen oder verändert, kann das zu Problemen führen. Wenn eine Änderung in so einem Thread dazu führt, dass WPF-Controls aktualisiert werden, wird das definitiv zu einem Fehler führen.
Soll heißen:
Sehr aufwändige, langandauernde Aufgaben gehören in einen eigenen Thread, Du solltest aber darauf achten, dass die Zugriffe synchronisiert werden.
Änderungen, welche die UI aktualisieren, müssen also wieder in den UI-Thread synchronisiert werden, dafür ist dann Dispatcher.InvokeAsync geeignet.
Besser ist aber, dass Du Dispatcher.InvokeAsync gar nicht brauchst.
Wenn Du z.B. über einen Button die Methode aufrufen lässt, wird diese Methode im UI-Thread ausgeführt. Wenn Du einen Task hast, dann kannst Du dich darauf verlassen, dass der Code nach dem await wieder im UI-Thread ausgeführt wird. Du musst also nur darauf achten, dass Du keine Aufgaben an Task.Run übergibst, die in einem extra Thread laufen können.
Oder in kurz:
Dein Code in RefreshAsync ist mit Task.Run, genau so wie er ist, richtig, wenn GetItems nicht die UI aktualisiert und auch anderweitig in einem eigenen Thread laufen darf.
Geht das nicht und Du hast darin z.B. Datenbank-Aufrufe (z.B. wenn das ORM Async unterstützt), dann solltest Du keinen neuen Task starten, Du solltest stattdessen die Datenbank-AUfrufe awaiten, der Rest wird dann wieder im UI-Thread ausgeführt.
public async Task RefreshAsync()
{
IEnumerable<T> items = await GetItemsAsync();
ItemList.Replace(items);
}
ublic async Task<IEnumerable<T>> GetItemsAsync()
{
// Do something on UI-Thread
var data = await Connection.GetDataAsync();
// Do something on UI-Thread
var readItems = await ReadDataAsync(data);
// Do something on UI-Thread
return readItems;
}
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.
Hallo sugar76,
Dann müsste man aber GetItems selbst als asynchron definieren oder?
Ja.
mfG Gü
Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.
"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"