【问题标题】:Task blocks UI from refreshing in WPF任务阻止 UI 在 WPF 中刷新
【发布时间】:2012-05-31 16:37:34
【问题描述】:

我正在构建一个 WPF 应用程序,当您从列表中选择一个时,它会将 powerpoint 转换为 WPF 元素。 我正在使用 MVVM light 将 ViewModel 绑定到我的视图并添加 ViewModel 之间的通信。

我有两个视图:OpenLocalView 和 PresentationView。当我在 OpenLocalView 中选择一个 powerpoint 时,MVVM 灯将向 PresentationView 的 ViewModel 和 MainViewModel 发送一条消息,其中包含该 powerpoint 的路径。 MainViewModel 将视图切换到 PresentationView,PresentationViewModel 执行此代码来转换 powerpoint,完成后,设置当前幻灯片,使其显示在 PresentationView 中:

  public void StartPresentation(string location)
  {
        var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
        Loading = true;
        Task.Factory.StartNew(() =>
            {
                var converterFactory = new ConverterFactory();
                var converter = converterFactory.CreatePowerPointConverter();
                _slides = converter.Convert(location).Slides;
            }, 
            CancellationToken.None, 
            TaskCreationOptions.LongRunning, 
            scheduler).ContinueWith(x =>
                {
                    Loading = false;
                    CurrentSlide = _slides.First();
                }, 
                CancellationToken.None, 
                TaskContinuationOptions.OnlyOnRanToCompletion, 
                scheduler);
   }

设置 Loading 属性后,视图会更新为“正在加载”消息,以使 UI 更具响应性:

    public Boolean Loading
    {
        get { return _loading; }
        set
        {
            _loading = value;
            RaisePropertyChanged("Loading");
        }
    }

问题是,这在我第一次加载 powerpoint 时正确执行:视图切换到 PresentationView,显示“正在加载”消息,转换完成后,消息消失并显示幻灯片。但是当我回到 OpenLocalView 并选择另一个 powerpoint 时,OpenLocalView 挂起,并在转换器完成后切换到 PresentationView,根本没有显示“正在加载”消息。

作为参考,我将添加一些更相关的代码。

当在 OpenLocalViewModel 中选择一个 powerpoint 时执行:

    private void PerformOpenPresentation(string location)
    {
        Messenger.Default.Send<OpenPowerPointMessage>(new OpenPowerPointMessage {Location = location});
    }

MainViewModel 订阅了 messenger 并切换视图:

Messenger.Default.Register<OpenPowerPointMessage>(this,
            delegate
            {
                if (_presentation == null) _presentation = new PresentationView();
                CurrentView = _presentation;
            });

PresentationViewModel 也订阅了 messenger 并执行上面显示的方法:

Messenger.Default.Register<OpenPowerPointMessage>(this, message => StartPresentation(message.Location));

那么,我做错了什么?同样,它执行一次很好,然后不再执行,尽管执行的是相同的代码。

【问题讨论】:

    标签: c# wpf mvvm-light task-parallel-library


    【解决方案1】:

    当您已经开始转换时,用户界面可能尚未更新。尝试在将 Loading 设置为 true 和转换器线程开始之间等待几毫秒 :)

    【讨论】:

      【解决方案2】:

      看这里:

       var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
       Loading = true;
      
      Task.Factory.StartNew(() =>
              {
                  var converterFactory = new ConverterFactory();
                  var converter = converterFactory.CreatePowerPointConverter();
                  _slides = converter.Convert(location).Slides;
              }, 
              CancellationToken.None, 
              TaskCreationOptions.LongRunning, 
      here  ----> scheduler).ContinueWith(x =>
                  {
                      Loading = false;
                      CurrentSlide = _slides.First();
                  }, 
                  CancellationToken.None, 
                  TaskContinuationOptions.OnlyOnRanToCompletion, 
                  scheduler);
      

      您正在同步上下文(即 UI 线程)上启动“长时间运行”任务。 在长时间运行的任务中摆脱调度程序,让它继续运行。 :)

      【讨论】:

      • 然后我得到一个异常:“调用线程必须是 STA,因为许多 UI 组件都需要这个。”。这是因为在转换器中,我正在创建 WPF 元素。
      • @Hanz 好吧,我没有更好的主意,因为你创建 UI 元素,你不能在其他线程中创建它们,你必须在 UI 线程上做 - 但这也意味着 UI在创建期间将保持无响应。我通常只是在后台准备大量数据,然后在继续时将其绑定到 UI,这样可以提高响应能力。在你的情况下它可能不可行,但我现在很难说什么是最好的解决方案。
      • @Doctor 我可以忍受 UI 在转换时无响应的事实,但我只想在转换开始之前显示“正在加载”消息,然后将其删除。不过还是谢谢。
      • @Hanz 那么,您可以按顺序执行此操作,无需任务。将您的(可能绑定DependencyPropertyLoading 设置为true,调用准备,然后将其更改为false。我不能说或保证结果会是什么,但这听起来像是最简单的解决方案。 :)
      • 然后 OpenLocalView 冻结,转换完成后,它变为 PresentationView 并打开演示文稿。没有“加载”消息或任何东西。这可能与消息订阅者执行其方法的顺序有关吗?
      【解决方案3】:

      我建议将“Loading = true”移到任务开始块中。但请确保在设置“Loading”值时使用调度程序。我可能不会给出问题的实际原因,但值得一试......

      这样的事情可能会有所帮助..

      Task.Factory.StartNew(() =>
                  {
                     System.Windows.Application.Current.Dispatcher.BeginInvoke((Action)delegate()
                       {
                           Loading = true;
                        });
                      var converterFactory = new ConverterFactory();
                      var converter = converterFactory.CreatePowerPointConverter();
                      _slides = converter.Convert(location).Slides;
                  }
      

      【讨论】:

      • 然后 OpenLocalView 冻结,转换完成后,它变为 PresentationView 并打开演示文稿。没有“加载”消息或任何东西......
      【解决方案4】:

      好的,我是用 windows 窗体做的,但我认为是一样的

      我在Task线程中创建了一个Label,并调用了表单的Invoke

      public partial class Form1 : Form
      {
          public Form1()
          {
              InitializeComponent();
          }
      
          public void AddControl(Control control)
          {
              if (InvokeRequired)
              {
                  this.Invoke(new Action<Control>(AddControl), new object[] { control });
                  return;
              }
              this.Controls.Add(control);
          }
      
          private void Form1_Load(object sender, EventArgs e)
          {
      
              Task.Factory.StartNew(() =>
                  {
                      var label = new Label
                      {
                          Location = new Point(0, 0),
                          Text = "hola",
                          ForeColor = Color.Black
                      };
                      this.Invoke(new Action<Control>(AddControl), new object[] { label });
                  });
          }
      }
      

      编辑

      好的,如何使用 Dispather.Invoke 我不确定这是否会阻塞 UI....

        public partial class MainWindow : Window
      {
          public MainWindow()
          {
              InitializeComponent();
          }
      
          public void AddControl()
          {
              var l = new Label
              {
                  Content = "Label",
                  Height = 28,
                  HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
                  Margin = new Thickness(209, 118, 0, 0),
                  Name = "label1",
                  VerticalAlignment = System.Windows.VerticalAlignment.Top
              };
      
              Grid.Children.Add(l);
          }
      
      
          private void Grid_Loaded(object sender, RoutedEventArgs e)
          {
              Task.Factory.StartNew(() =>
              {
                  Dispatcher.Invoke(new Action(AddControl), null);
              });
          }
      }
      

      【讨论】:

      • 使用 MVVM 和 WPF 时,情况完全不同。 vM 无权访问 UI 控件。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-15
      • 1970-01-01
      • 2016-02-14
      • 1970-01-01
      • 2012-09-23
      • 1970-01-01
      相关资源
      最近更新 更多