W mojej aplikacji MVVM mój model widoku wywołuje 3 różne metody obsługi, konwertuje dane z każdego do wspólnego formatu, a następnie aktualizuje interfejs użytkownika za pomocą powiadomień o właściwościach/obserwowalnych kolekcji itp.Jak kontynuować po wielu zadaniach bez blokowania wątku interfejsu użytkownika?
Każda metoda w warstwie usługi rozpoczyna nową Task
i zwraca Task
do modelu widoku. Oto przykład jednej z moich metod obsługi.
public class ResourceService
{
internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
var t = Task.Factory.StartNew(() =>
{
//... get resources from somewhere
return resources;
});
t.ContinueWith(task =>
{
if (task.IsFaulted)
{
errorCallback(task.Exception);
return;
}
completedCallback(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
return t;
}
}
Oto kod wywołujący i inne istotne elementy modelu widoku ...
private ObservableCollection<DataItem> Data = new ObservableCollection<DataItem>();
public ICollectionView DataView
{
get { return _dataView; }
set
{
if (_dataView != value)
{
_dataView = value;
RaisePropertyChange(() => DataView);
}
}
}
private void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.WaitAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
private Task LoadResources()
{
return ResourceService.LoadResources(resources =>
{
foreach(var r in resources)
{
var d = convertResource(r);
Data.Add(d);
}
},
error =>
{
// do some error handling
});
}
to prawie działa, ale istnieje kilka małych problemów.
Numer 1: W wywołaniu SetBusy
na samym początku, przed rozpoczęciem jakichkolwiek zadań i zanim zadzwonię pod numer WaitAll
, ustawię właściwość IsBusy
na true. To powinno zaktualizować interfejs użytkownika i pokazać kontrolkę BusyIndicator, ale nie działa. Próbowałem również dodawać proste właściwości łańcuchów i wiązać je, a także nie są one aktualizowane. Funkcja IsBusy jest częścią klasy bazowej i działa w innych modelach widoku, w których nie ma więcej niż jednego zadania, więc nie sądzę, aby wystąpił problem z powiadomieniem o właściwościach lub powiązaniem danych w XAML.
Wszystkie powiązania danych wydają się być aktualizowane po zakończeniu całej metody. Nie widzę żadnych "wyjątków po raz pierwszy" ani błędów wiązania w oknie wyjściowym, co prowadzi mnie do przekonania, że wątek interfejsu użytkownika jest w jakiś sposób blokowany przed wywołaniem WaitAll.
Numer 2: Wydaje mi się, że zwracam niewłaściwe Zadania z metod serwisowych. Chcę, aby wszystko po WaitAll
było uruchamiane po przekonwertowaniu przez model widoku wszystkich wyników ze wszystkich metod usługowych w wywołaniach zwrotnych. Jeśli jednak zwrócę zadanie kontynuacji z metody serwisowej, kontynuacja nigdy nie zostanie wywołana i WaitAll
czeka na zawsze. Dziwną rzeczą jest to, że kontrolka interfejsu użytkownika powiązana z ICollectionView faktycznie wyświetla wszystko poprawnie. Zakładam, że dzieje się tak dlatego, że Data jest kolekcją obserwowalną, a CollectionViewSource jest świadomy zmiany zmienionych zdarzeń.
Odkładałem używanie async/czekam, ponieważ nie jestem pewien, czy to "działa" z Poleceniami WPF i Prism DelegateCommand. Task.ContinueWhenAll wydaje się nie istnieć dla mnie, czy muszę odwołać się do niektórych bibliotek rozszerzeń TPL? – BenCr
@BenCr Przepraszamy - to jest TaskFactory.ContinueWhenAll. Ogólnie rzecz biorąc, rzeczy oczekujące/asynchroniczne działają * lepiej * niż kontynuacje zadań z WPF. Główną różnicą jest to, że nie musisz przeskakiwać przez szalone obręcze, aby poradzić sobie z wyjątkami. –
Dzięki Reed, wygląda na to, że działa znacznie lepiej. Mam nadzieję, że ktoś może być w stanie podać wyjaśnienie blokady przed wywołaniem WaitAll, ale to z pewnością rozwiązało problemy 1 i 2. Dam ci asynchroniczne/oczekiwane rzeczy w następnej iteracji i zobaczę, jak sobie poradzę. – BenCr