0

我正在尝试在后台线程中加载文件,同时在 UI 中显示进度条。看来BinaryFormatter.Deserialize功能以及更新都ProgressBar需要在一个STA线程上运行。我正在使用 TPL 库将 T 传递给askScheduler.FromCurrentSynchronizationContext()加载任务,但这似乎将progressbar更新和文件加载安排到同一个线程上,以便它们串行发生而不是并行发生。

我尝试过TaskScheduler.Default传入LoadModelTask,但这会在BinaryFormatter.Deserialize调用时出现 STA 错误。

是否有另一种方法可以在不需要我冻结 UI 线程的后台加载 WPF 对象?

我的代码:

        private void openFile()
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.DefaultExt = FILE_EXTENSION;
            dialog.Filter = "MFlow Documents|*.mpex;*.mpxc;*.mpoc";

            Nullable<bool> result = dialog.ShowDialog();
            if (result == true)
            {                
                string filename = dialog.FileName;

                var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
                CancellationTokenSource source=new CancellationTokenSource();
                CancellationToken token = source.Token;

                feedbackWindow = new FeedbackWindow();
                feedbackWindow.ProgressBar.IsIndeterminate = true;
                feedbackWindow.ProgressLabel.Content = "Opening " + filename;
                feedbackWindow.Show();

                Task<Model> loadModelTask=
                    Task.Factory.StartNew<Model>(() => LoadModel(filename),
                    token, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
                loadModelTask.ContinueWith(task => AfterLoadModel(task), scheduler);

            }
        }

        private static Model LoadModel(string filename)
        {
            Model returnModel;
            string extension = filename.Split('.')[filename.Split('.').Length - 1];

            Stream stream = File.Open(filename, FileMode.Open);
                using (var gZipStream = new GZipStream(stream, CompressionMode.Decompress))
                {
                    BinaryFormatter formatter = new BinaryFormatter();

                    stream.Seek(0, SeekOrigin.Begin);
                    var test = formatter.Deserialize(gZipStream);
                    returnModel = (Model)formatter.Deserialize(gZipStream);

                    gZipStream.Close();
                    stream.Close();
                }
            }
            return returnModel;
        }

        private void AfterLoadModel(Task<Model> task)
        {
            try
            {
                task.Wait();
                switch (task.Status)
                {
                    case TaskStatus.RanToCompletion:
                        ModelResult = task.Result;
                        feedbackWindow.Close();
                        break;
                    default:
                        break;
                }

            }
            catch (AggregateException ex)
            {
                // For demonstration purposes, show the OCE message. 
                foreach (Exception v in ex.InnerExceptions)
                {
                    Debug.WriteLine("msg: " + v.Message);
                }
            }
        }
4

1 回答 1

2

是否有另一种方法可以在不需要我冻结 UI 线程的后台加载 WPF 对象?

绝对,绝对不是。

WPF UI 元素具有线程关联性,这意味着只能使用实例化它们的线程来触摸它们。由于父 UIElement 在其子 UIElement 中调用方法,因此负责构建可视化树中根元素的线程必须是构建可视化树中所有UIElement 的同一线程。

有关详细信息,请阅读 MSDN 上的这篇文章。

如果您正在序列化/反序列化 UIElements,那么您已经做错了。您应该根据反序列化、绑定到您的 UI 并通过 DataTemplates 中描述的 UIElements 显示的模型(POCO 类)和 ViewModels(本质上是模型,但带有更改通知)来描述您的数据。这通常被称为 MVVM 模式,它允许您异步处理数据,而无需(过多)担心 UI 中的线程(如果操作正确)。

如果您只是想在长时间运行的操作期间更新您的 UI,最好将一个属性绑定到您的 UI,该属性描述您的操作有多远,或者使用 DispatcherTimer 更新您的 UI(读取属性的当前状态) 或使用异步绑定来自动更新 UI(我相信 INPC 属性绑定会自动为您处理对 UI 线程的封送调用)。

最后一点,如果一个操作需要不到两秒钟,不要为了显示进度条而放慢速度。您的用户会感谢您。

于 2013-01-07T21:37:36.747 回答