2

Every time when timer invoke UpdateDocumentsListFromServer UI freezes for 3 seconds. How to update list in async style under .net 3.5?

ViewModel:

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 look like

    public ObservableCollection<ShippingDocument> GetDocumentsFromServer()
    {
        System.Threading.Thread.Sleep(3000);
        return new ObservableCollection<ShippingDocument> { new ShippingDocument { Name = "Test" } };
    }
Sinatr
  • 20,892
  • 15
  • 90
  • 319
A191919
  • 3,422
  • 7
  • 49
  • 93
  • It freezes because you have `Sleep(3000)`. Perhaps you want to set binding [IsAsync](https://msdn.microsoft.com/en-us/library/system.windows.data.binding.isasync(v=vs.110).aspx) (for this it has to be a property)? Another *option* is to define `GetDocumentsFromServer` as `async` and use asynchronous methods inside (e.g. `await Task.Delay()` or even `await Task.Run(() => Thread.Sleep())` if you wish). – Sinatr Jul 25 '16 at 12:36
  • @Sinatr, Yes i know that Sleep(3000) freez UI. It emulate long running work. In .net 3.5 there is no any async, await methods. – A191919 Jul 25 '16 at 12:40
  • 5
    I would suggest to go for [Timer](https://msdn.microsoft.com/en-us/library/zdzx8wx8.aspx) instead of `DispactherTimer`. `DispactherTimer` will access the UIThread where as `Timer` use thread from threadpool. – Gopichandar Jul 25 '16 at 12:41
  • Adding to @Gopichandar comment: modifying `ObservableCollection<>` from another thread is [bad idea](http://stackoverflow.com/q/2104614/1997232), but you can [invoke](http://stackoverflow.com/a/187081/1997232) that `foreach`. – Sinatr Jul 25 '16 at 12:45
  • There are already other posts about asynch in .net 3.5... For [example](http://stackoverflow.com/questions/22516303/how-can-i-get-the-equivalent-of-taskt-in-net-3-5) I think this could be a possible duplicate (also considering that the OP is not giving any specific feedback to the different answers :-) ) –  Jul 26 '16 at 04:01

4 Answers4

2

You could also use a background worker that reports progress to the 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];
  • obviously the line with the RunWorkerCompleted is irrelevant as long as it is not fired... –  Jul 25 '16 at 14:24
2

Just offload it to a new thread using Task and Async/Await like so:

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

Keep in mind this updates on a different thread then the UI. So it is not allowed to touch anything on the UI thread or you will get threading issues. So if shippingDocuments was created on the UI thread and is not thread-safe you could instead return a collection of items then add them:

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);
        }
Kelly
  • 6,992
  • 12
  • 59
  • 76
  • A couple of things 1) I assume you're not using .Net 3.5 with this exact code, but you forgot to mention this part of the OP question :-) ... 2) Sure, ShippingDocuments is UI –  Jul 26 '16 at 04:09
0

Use a regular Timer and only dispatch the access to shippingDocuments.

H.B.
  • 166,899
  • 29
  • 327
  • 400
0

As mentioned in comment, you can make use of Timers instead of DispatcherTimer. DispactherTimer will access the UIThread where as Timer use different thread from threadpool.

Also, you can dispatch an action to UIThread from different thread

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

Hope that helps.

Gopichandar
  • 2,742
  • 2
  • 24
  • 54