繁体   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