如何在.NET 4中的后台线程中使用二进制格式化反序列化?

时间:2013-01-07 21:30:17

标签: wpf serialization task-parallel-library background-process

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

我尝试将TaskScheduler.Default传递到LoadModelTask,但这会在BinaryFormatter.Deserialize来电时出现STA错误。

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

我的代码:

        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);
                }
            }
        }

1 个答案:

答案 0 :(得分:2)

还有另一种在后台加载WPF对象的方法,不需要我冻结UI线程吗?

绝对,肯定不是。

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

有关详情,请read this article on MSDN.

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

如果您只是在长时间运行的操作中尝试更新用户界面,最好将属性绑定到您的用户界面,该属性描述您的操作距离并使用更新您的用户界面DispatcherTimer(读取属性的当前状态)或使用异步绑定自动更新UI(我相信INPC属性绑定会自动为您调度UI线程的编组调用)。

最后一点,如果操作时间不到两秒,请不要将其减慢以显示进度条。您的用户会感谢您。