0

我想在後臺線程中加載文件,同時在UI中顯示進度條。看起來BinaryFormatter.Deserialize函數以及ProgressBar的更新需要在STA線程上運行。我使用的是第三方物流庫到T askScheduler.FromCurrentSynchronizationContext()傳遞到裝載任務,但這似乎給progressbar更新和文件加載安排到相同線程,讓他們並行進行,而不是連續的。如何在.NET 4的後臺線程中使用二進制格式化器進行反序列化?

我試過將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); 
       } 
      } 
     } 

回答

2

是否有另一種方式來加載WPF中,不需要我凍結UI線程的背景對象?

絕對,肯定不是。

WPF UI元素具有線程親和力,這意味着它們只能使用它們被實例化的線程來觸及。由於母公司UI元素調用他們的孩子UI元素的方法,在同一個線程,你的視覺樹內負責根元素的結構必須是構建可視化樹中的所有 UI元素在同一個線程。

欲瞭解更多信息,請read this article on MSDN.

如果你序列化/反序列化的UI元素你已經做錯了。您應該根據模型(POCO類)和ViewModels(實質上是模型但具有更改通知)來描述數據,這些數據是反序列化的,綁定到您的UI並通過DataEmplates中描述的UIElements顯示。這通常稱爲MVVM模式,並允許您異步操作數據,而不用擔心(很多)關於UI中的線程(如果正確完成)。

如果你只是想一個長時間運行的操作期間更新UI其更好地有一個屬性綁定到(在讀你的UI描述沿着您的操作有多遠,要麼更新使用DispatcherTimer您的UI該屬性的當前狀態)或使用異步綁定來自動更新UI(我相信INPC屬性綁定會自動處理對UI線程的封送處理調用)。

而且,最後要注意,如果兩個下秒操作時間,不要慢下來只是爲了顯示一個進度條。你的用戶會感謝你。

+0

綁定沒有任何元帥到UI線程。在非UI線程中更改屬性並引發INPC事件與從該線程觸及UIElement一樣糟糕。 – zmb

+0

@zmb想打動你的心思?旋轉一個樣本並自己嘗試。不在我的辦公室或我會告訴你他的規格。 – Will

+0

啊 - 看起來你是正確的(從.NET 3.5開始)。我認爲我所說的對於3.5之前的Silverlight,.NET以及集合和'INotifyCollectionChanged'仍然是正確的。 – zmb

相關問題