【问题标题】:DispatcherTimer WPF asyncDispatcherTimer WPF 异步
【发布时间】:2016-07-25 12:33:37
【问题描述】:

每次定时器调用UpdateDocumentsListFromServer UI 冻结 3 秒。如何在.net 3.5下以异步方式更新列表?

视图模型:

public class ShippingDocumentsRegisterViewModel : ViewModelBase
    {
        ShippingDocumentsModel model = new ShippingDocumentsModel();

        DispatcherTimer timer = new DispatcherTimer();

        BackgroundWorker BW = new BackgroundWorker();

        public ShippingDocumentsRegisterViewModel()
        {
            timer = new DispatcherTimer();
            timer.Tick += new EventHandler(UpdateDocumentsListFromServer);
            timer.Interval = new TimeSpan(0, 0, 10);
            timer.Start();

            this.Columns = model.InitializeColumns();
            BW.DoWork += UpdateDocumentsList;
            BW.RunWorkerAsync();
        }

        public void UpdateDocumentsList(object o, EventArgs args)
        {
            this.ShippingDocuments = model.GetDocuments();
        }

        public void UpdateDocumentsListFromServer(object o, EventArgs args)
        {
            // Taking a lot of time. How to do it async?
            var tempDocuments = model.GetDocumentsFromServer();
            foreach (var item in tempDocuments)
            {
                this.shippingDocuments.Add(item);
            }
            //
        }

        private ObservableCollection<ShippingDocument> shippingDocuments;

        public ObservableCollection<ShippingDocument> ShippingDocuments
        {
            get
            {
                return shippingDocuments;
            }

            private set
            {
                shippingDocuments = value;
                RaisePropertyChanged("ShippingDocuments");
            }
        }

        public ObservableCollection<ShippingDocumentColumDescriptor> Columns { get; private set; }

    }

GetDocumentsFromServer 看起来像

    public ObservableCollection<ShippingDocument> GetDocumentsFromServer()
    {
        System.Threading.Thread.Sleep(3000);
        return new ObservableCollection<ShippingDocument> { new ShippingDocument { Name = "Test" } };
    }

【问题讨论】:

  • 冻结是因为你有Sleep(3000)。也许您想设置绑定IsAsync(为此它必须是一个属性)?另一个选项是将GetDocumentsFromServer定义为async并在内部使用异步方法(例如await Task.Delay()或者甚至await Task.Run(() =&gt; Thread.Sleep()),如果你愿意的话)。
  • @Sinatr,是的,我知道 Sleep(3000) 会冻结 UI。它模拟长时间运行的工作。在 .net 3.5 中没有任何 async、await 方法。
  • 我建议使用Timer 而不是DispactherTimerDispactherTimer 将访问 UIThread,而 Timer 使用线程池中的线程。
  • 添加到@Gopichandar评论:从另一个线程修改ObservableCollection&lt;&gt;bad idea,但你可以invoke那个foreach
  • .net 3.5 中已经有其他关于异步的帖子...对于example,我认为这可能是重复的(同时考虑到 OP 没有对不同的答案提供任何具体的反馈:-) )

标签: c# wpf asynchronous .net-3.5


【解决方案1】:

您还可以使用向 UI 报告进度的后台工作人员

public ShippingDocumentsRegisterViewModel()
{

    BW.DoWork += UpdateDocumentsListFromServer;
    BW.RunWorkerCompleted += BW_RunWorkerCompleted;

    BW.WorkerReportsProgress = true;
    BW.ProgressChanged += UpdateGui;
    BW.RunWorkerAsync();
}
public void UpdateGui(object o, EventArgs args)
{
    foreach (var item in tempDocuments)
    {
        this.shippingDocuments.Add(item);
    }
}
public void UpdateDocumentsListFromServer(object o, EventArgs args)
{

    while (true) {
        System.Threading.Thread.Sleep(3000);

        tempDocuments = GetDocumentsFromServer();
        BW.ReportProgress(0);

    }
}

int num = 0;
public ShippingDocument[] GetDocumentsFromServer()
    {
        System.Threading.Thread.Sleep(3000);
        return new ShippingDocument[1] { new ShippingDocument { Name = "Test" + num++} };
    }

private ShippingDocument[] tempDocuments = new ShippingDocument[0];

【讨论】:

  • 显然 RunWorkerCompleted 的行是无关紧要的,只要它没有被解雇...
【解决方案2】:

只需使用 Task 和 Async/Await 将其卸载到新线程,如下所示:

public async void UpdateDocumentsListFromServer(object o, EventArgs args)
        {
            // This will execute async and return when complete
            await Task.Run(()=>{
              var tempDocuments = model.GetDocumentsFromServer();
              foreach (var item in tempDocuments)
              {
                  this.shippingDocuments.Add(item);
              }
            });
            //
        }

请记住,此更新在与 UI 不同的线程上进行。所以不允许在 UI 线程上触摸任何东西,否则你会遇到线程问题。因此,如果 shippingDocuments 是在 UI 线程上创建的并且不是线程安全的,您可以改为返回一个项目集合然后添加它们:

public async void UpdateDocumentsListFromServer(object o, EventArgs args)
        {
            // Execute on background thread and put results into items
            var items = await Task.Run(()=>{
              var tempDocuments = model.GetDocumentsFromServer();                  
              return tempDocuments;
            });
            //add occurs on UI thread.
            this.shippingDocuments.AddRange(tempDocuments);
        }

【讨论】:

  • 几件事 1)我假设您没有使用带有此确切代码的 .Net 3.5,但是您忘记提及 OP 问题的这一部分 :-) ... 2)当然, ShippingDocuments 是 UI
【解决方案3】:

使用常规的Timer 并仅将访问权限分配给shippingDocuments

【讨论】:

    【解决方案4】:

    正如评论中提到的,您可以使用Timers 而不是DispatcherTimerDispactherTimer 将访问 UIThread,因为 Timer 使用与线程池不同的线程。

    另外,你可以从不同的线程向 UIThread 分派一个动作

    Application.Current.Dispatcher.BeginInvoke(new Action(() =>
               {
                   //Do some UI stuffs                   
               }));
    

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-14
      • 2013-11-02
      • 1970-01-01
      相关资源
      最近更新 更多