[英]async Task blocking UI
我有以下情況:在我使用 MVVM 的 WPF 應用程序中(我對 MVVM 還很陌生)我有一個包含 DataGrid 的 Window。 加載 Window 時,我想使用實體框架使用數據庫中的條目填充 DataGrid。 此外,某個列應在編輯模式下使用 ComboBox,該列也從數據庫中填充(多對多關系)。 這些項目也應該在 Window 加載時加載。 哦,是的,當然,這應該異步完成,這樣數據庫查詢就不會阻塞 UI。
我讀過 Stephen Cleary 的這些優秀博客文章: https://t1p.de/c12y8和https://t1p.de/xkdh 。
我選擇了同步構造我的 ViewModel 並從 Window-Loaded 事件調用異步Initialize
方法的方法。 Initialize
方法然后觸發兩個查詢。
視圖模型:
public class ViewModel : ViewModelBase
{
// this uses a slightly modified class from the first blog post
private NotifyTaskCompletion databaseAction;
public NotifyTaskCompletion DatabaseAction
{
get => databaseAction;
private set
{
databaseAction = value;
NotifyPropertyChanged();
}
}
public ViewModel()
{
// nothinc asynchronous going on here
}
public void Initialize()
{
DatabaseAction = new NotifyTaskCompletion(InitializeAsync());
}
private async Task InitializeAsync()
{
List<Task> tasks = new List<Task>();
tasks.Add(FirstQueryAsync());
tasks.Add(SecondQueryAsync());
await Task.WhenAll(tasks);
}
private async Task FirstQueryAsync()
{
using (var context = new SampleContext())
{
var query = await context.Beds.ToListAsync();
if (query.Count > 0)
{
beds = new ObservableCollection<Bed>();
query.ForEach(bed => beds.Add(bed));
}
else
{
LoadBedsFromFile(ref beds);
foreach (var bed in beds)
{
context.Beds.Add(bed);
}
await context.SaveChangesAsync();
}
}
}
private void LoadBedsFromFile(ref ObservableCollection<Bed> list)
{
if (File.Exists("Beds.xml"))
{
FileStream fs = new FileStream("Beds.xml", FileMode.Open);
XmlSerializer serializer = new XmlSerializer(typeof(ObservableCollection<Bed>));
list = (ObservableCollection<Bed>)serializer.Deserialize(fs);
fs.Close();
}
}
private async Task SecondQueryAsync()
{
using (var context = new SampleContext())
{
var query = await context.Samples.Where(...)
.Include(...)
.ToListAsync();
foreach (Sample item in query)
{
// each entry is put into a ViewModel itself
SampleViewModel vm = new SampleViewModel(item);
// sampleClass.Samples is an ObservableCollection
sampleClass.Samples.Add(vm);
}
}
}
Window:
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
ViewModel vm = this.TryFindResource("viewModel") as ViewModel;
if (vm != null)
vm.Initialize();
}
}
現在的問題是:UI 沒有響應,並且在初始化完成之前不會更新。 即使我使用Task.Run(() => vm.Initialize());
. 奇怪的是:如果我將Task.Delay()
放入InitializeAsync
方法中,加載 animation 會按預期顯示,並且 UI 會響應。 例如,如果我將 Delay 放入SecondQueryAsync
中,UI 會凍結幾秒鍾,然后加載 animation 在延遲期間旋轉。 我懷疑這可能是創建 DbContext 的一些問題,但我無法查明這一點。
我最終解決了這個問題。 TheodorZoulias 的評論和他發布的await Task.Run vs await鏈接給了我解決方案的提示。
替換new NotifyTaskCompletion(InitializeAsync());
使用new NotifyTaskCompletion(Task.Run(() => InitializeAsync()));
不幸的是引發了其他問題,因為我不能簡單地從該任務的上下文中修改ObservableCollection
。
我真的很喜歡用 async-await 編寫代碼。 問題是當它遇到非異步代碼時。 因為我懷疑創建 DbContext 的同步調用阻塞了線程。
所以這就是我解決它的方法 - 也許它可以幫助某人:
首先,我使用工廠方法來異步創建 DbContext:
public class SampleContext : DbContext
{
private SampleContext() : base()
{
...
}
public static async Task<SampleContext> CreateAsync()
{
return await Task.Run(() => { return new SampleContext(); }).ConfigureAwait(false);
}
}
然后我重寫了查詢數據庫的方法,使它們不修改ObservableCollection
本身而是返回一個列表。 第二種方法將返回值作為參數並修改ObservableCollection
。 這樣我就可以使用ConfigureAwait
來配置上下文。
private async Task<List<Sample>> SecondQueryAsync()
{
var context = await SampleContext.CreateAsync().ConfigureAwait(false);
var query = await context.Samples.Where(...)
.Include(...)
.ToListAsync().ConfigureAwait(false);
context.Dispose();
return query;
}
private async Task InitializeAsync()
{
List<Task> tasks = new List<Task>();
var firstTask = FirstQueryAsync();
var secondTask = SecondQueryAsync();
tasks.Add(firstTask);
tasks.Add(secondTask);
await Task.WhenAll(tasks);
if (tasks.Any(t => t.IsFaulted))
{
Initialized = false;
}
else
{
Initialized = true;
FillFirstList(firstTask.Result);
FillSecondList(secondTask.Result);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.