【问题标题】:How to deserialize with a binaryformatter in a background thread in .NET 4?如何在 .NET 4 的后台线程中使用二进制格式化程序反序列化?
【发布时间】:2013-01-08 05:44:16
【问题描述】:

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

我尝试将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);
                }
            }
        }

【问题讨论】:

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


    【解决方案1】:

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

    绝对不会。

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

    更多信息请read this article on MSDN.

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

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

    最后要注意的是,如果一个操作需要不到两秒的时间,不要为了显示进度条而放慢它。您的用户会感谢您的。

    【讨论】:

    • 绑定不会将任何内容编组到 UI 线程。在非 UI 线程中更改属性并引发 INPC 事件与从该线程触摸 UIElement 一样糟糕。
    • @zmb 想大吃一惊吗?旋转样品并自己尝试。不在我的办公室,否则我会告诉你他的规格。
    • 啊 - 看起来你是正确的(从 .NET 3.5 开始)。我认为我所说的对于 Silverlight、3.5 之前的 .NET 以及集合和 INotifyCollectionChanged 仍然适用。
    • @zmb 但是......它让你大吃一惊吗?做了我的。也让我的代码更紧凑。
    • @Will,我可能用短语“WPF Object”给出了错误的信息。我不是在实例化 UI 元素,而是在加载视图模型及其关联模型。这些对象都没有与之关联的任何直接 UI 对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-24
    • 2014-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-23
    • 2014-01-04
    相关资源
    最近更新 更多