Страницы

Поиск по вопросам

четверг, 7 марта 2019 г.

Асинхронный вызов метода в WPF

У меня имеется WPF-приложение, работающее с базой данных. Количество записей в базе довольно большое и постоянно растёт. В проекте используется ORM(EF 6). Имеется некий класс, работающий непосредственно с контекстом базы данных:
public class Store : IStore {...}
В интерфейсе класса определён ряд методов, код в которых обращается непосредственно к базе через EF-контекст, соответственно, выполнение метода занимает довольно длительное время, что может вызвать простой интерфейса пользователя, если я всё правильно понимаю. Следовательно, нужно вынести операции получения данных из базы в отдельные потоки. Так, скажем, в Store определен метод:
public ICollection GetAllProducts() {...}
Если я правильно понимаю, мне необходимо добавить его асинхронную реализацию:
public async Task> GetAllProductsAsync() { return await Task>.Factory.StartNew(GetAllProducts) }
Как правильно воспользоваться таким методом в самом приложении в ViewModel, Скажем, чтобы, пока данные загружаются, в StatusBar отображался прогресс загрузки данных, а DataGrid отобразил результат как только данные загрузятся?


Ответ

Окей, давайте начнём с Task.Factory.StartNew. Это нужно только если ваш запрос к базе данных не поддерживает асинхронность сам, и требует выделения отдельного потока (кстати, лучше писать просто Task.Run).
Для свежего Entity Framework это не так, асинхронные функции поддерживаются правильно, из коробки: Entity Framework tutorial: async query and save
С асинхронностью на уровне базы данных вам не нужно создавать отдельные потоки.

По поводу прогресса, с этим хуже. EF не поддерживает информацию о прогрессе операции, так что вы можете просто вывести состояние «читаю», и считывать до тех пор, пока не закончите.
В случае, когда/если будет имплементирована поддержка прогресса, вам можно будет воспользоваться интерфейсом IProgress

Таким образом, код в VM будет выглядеть так:
IsLoading = true; var localData = await model.LoadDataAsync(); IsLoading = false; Data = localData;

Скорее всего, вы не захотите выкладывать модельные классы для View, поэтому вам понадобится обёртка, создающая VM-объекты для ваших entity:
// в модели IQueryable GetData();

// в VM IsLoading = true; var localData = new List(); await model.GetData().ForEachAsync(entity => localData.Add(new EntityVM(entity)); IsLoading = false; Data = localData;
Ну и, как верно отмечает @Pavel Mayorov, возможно вы захотите ловить исключения, так что вам понадобится try:
IsLoading = true; var localData = new List(); try { await model.GetData().ForEachAsync(entity => localData.Add(new EntityVM(entity)); Data = new ObservableCollection(localData); } catch { IsFailed = true; throw; } finally { IsLoading = false; }
Или, если вы хотите, чтобы данные появлялись не все вместе, а по мере подгрузки, наверное подойдёт просто
await model.GetData().ForEachAsync(entity => Data.Add(new EntityVM(entity));
(но здесь я не уверен, т. к. ни разу не пробовал).

Комментариев нет:

Отправить комментарий