簡體   English   中英

異步任務阻塞 UI

[英]async Task blocking UI

我有以下情況:在我使用 MVVM 的 WPF 應用程序中(我對 MVVM 還很陌生)我有一個包含 DataGrid 的 Window。 加載 Window 時,我想使用實體框架使用數據庫中的條目填充 DataGrid。 此外,某個列應在編輯模式下使用 ComboBox,該列也從數據庫中填充(多對多關系)。 這些項目也應該在 Window 加載時加載。 哦,是的,當然,這應該異步完成,這樣數據庫查詢就不會阻塞 UI。

我讀過 Stephen Cleary 的這些優秀博客文章: https://t1p.de/c12y8https://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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM