locked
[W8.1]ISupportIncrementalLoading doesn't stop after navigating away

    Question

  • Hi there, I suddenly found a strange behavior of collection implementing ISupportIncrementalLoading.
    Let's say we have a main page with ISupportIncrementalLoading collection bound to ListView. And we have another page where we can navigate to.
    When navigating to main page, the ISupportIncrementalLoading starts loading items until ListView thinks it's enough. I navigate to new page BEFORE ListView loaded all items it needs.
    My expected behavior: ListView stops loading new items as the page isn't visible now.
    Real behavior: ListView continues to load items endlessly, even after going away from the page. And it won't stop until gets HasMore == false.
    Can anyone help with this? This is absolutely wrong behavior.

    PS

    If I while navigation, set in ViewModel the collection to null and then restore it when coming back -- it seams to help, but that is too much to do, I think.

    Here's the code of my basic ISupportIncrementalLoading collection:

        public abstract class BaseIncrementalSupportCollection<T> :IList<T>,IList,INotifyCollectionChanged, ISupportIncrementalLoading, INotifyPropertyChanged
        {
            protected readonly List<T> storage;

            private bool isLoading;
            public bool IsLoading
            {
                get
                {
                    return isLoading;
                }
                set
                {
                    if (isLoading != value)
                    {
                        isLoading = value;
                        RaisePropertyChanged();
                    }
                }
            }

            public bool failed;

            public bool IsFailed
            {
                get { return failed; }
                set
                {
                    if (failed != value)
                    {
                        failed = value;
                        RaisePropertyChanged();
                    }
                }
            }

            public bool IsEmpty
            {
                get { return !HasMoreItems && Count == 0; }
            }

            protected BaseIncrementalSupportCollection()
            {
                storage = new List<T>();
            }

            public virtual IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
            {
                return Task.Run(()=>LoadMoreItems(count)).AsAsyncOperation();
            }

            public abstract bool HasMoreItems { get; }


            private async Task<LoadMoreItemsResult> LoadMoreItems(uint count)
            {
                IsLoading = true;
                IsFailed = false;
                try
                {
                    var items = await LoadMoreItemsOverride(count);
                    if (items == null)
                        return new LoadMoreItemsResult() {Count = 0};
                    if (items.Count > 0)
                    {
                        var prevEmptyState = IsEmpty;
                        foreach (var item in items)
                        {
                            var currItem = item;
                            await DispatchHelper.RunOnUiIfNecessary(async () =>
                            {
                                storage.Add(currItem);
                                RaiseCollectionChanged(
                                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, currItem,
                                        storage.Count - 1));
                            });
                        }
                        if(prevEmptyState!=IsEmpty)
                            RaisePropertyChanged("IsEmpty");

                    }
                    return new LoadMoreItemsResult() {Count = (uint) items.Count};
                }
                catch (Exception e)
                {
                    var aggregate = e as AggregateException;
                    if (aggregate != null)
                        e = aggregate.Flatten().InnerException;
                    IsFailed = true;
                    var handler = OnError;
                    if (handler != null)
                        DispatchHelper.RunOnUiIfNecessary(
                            () => handler(this, new IncrementallCollectionLoadErrorEventArgs(e)));
                    return new LoadMoreItemsResult() {Count = 0};
                }
                finally
                {
                    IsLoading = false;
                }

            }

            protected virtual void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                if (CollectionChanged != null)
                    CollectionChanged(this, e);
            }

            protected abstract Task<IList<T>> LoadMoreItemsOverride(uint count);

            [NotifyPropertyChangedInvocator]
            protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
            {
                var handler = PropertyChanged;
                if (handler != null)
                    DispatchHelper.RunOnUiIfNecessary(()=>handler(this, new PropertyChangedEventArgs(propertyName)));
            }

            public event PropertyChangedEventHandler PropertyChanged;
            public event EventHandler<IncrementallCollectionLoadErrorEventArgs> OnError;
            public event NotifyCollectionChangedEventHandler CollectionChanged;
        }


    Saturday, May 02, 2015 8:29 AM

Answers

  • Thanks for your answer, I hope this can be fixed in future versions. I've just found another way, adding additional bool field isStopped with methods Start, ForceStop setting it to false/true. This value is used when getting HasMoreItems like

    bool HasMoreItems{get{return !isStopped && DetermineIfHasMore()};}

    And simply by calling those to methods I can stop or continue loading the same collection generator.

    What do you think about this implementation?

    • Marked as answer by AvelN Thursday, May 07, 2015 9:43 AM
    Thursday, May 07, 2015 9:42 AM
  • Hi AvelN,

    Thank you for reporting this issue. I can reproduce it using MSDN code sample from here. https://code.msdn.microsoft.com/windowsapps/Data-Binding-7b1d67b5.

    The implementation in this sample has provided how to cancel the Loading approach, you just need to add some code when the main page navigate to others in its OnVavigatedFrom event handler.

    1, In order to do this, we should add a property to public the Cancel Token as following code in IncrementalLoadingBase.cs

    public CancellationTokenSource token = new CancellationTokenSource();
    
    
            public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    
            {
    
                if (_busy)
    
                {
    
                    throw new InvalidOperationException("Only one operation in flight at a time");
    
                }
    
    
                _busy = true;
    
    
                // use a new call method to instead the default one
    
                return LoadMoreItemsAsync(token.Token, count).AsAsyncOperation<LoadMoreItemsResult>();
    
                // default call method
    
               // return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count));
    
            }
    

    2, in GeneratorIncrementalLoadingClass.cs file, call a method to cancel the task.

    protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count)
    
            {
    
                //add this code to cancel the task
    
                c.ThrowIfCancellationRequested();
    
    
    
                uint toGenerate = System.Math.Min(count, _maxCount - _count);
    
                Debug.WriteLine(string.Format("load {0} at {1}", count.ToString(), DateTime.Now.ToString()));
    
                // Wait for work 
    
                await Task.Delay(10);
    
    
                // This code simply generates
    
                var values = from j in Enumerable.Range((int)_count, (int)toGenerate)
    
                             select (object)_generator(j);
    
    
                Random rnd=new Random();
    
                for (int i = 0; i < values.Count(); i++)
    
                {
    
                    Employee emp = values.ElementAt(i) as Employee;
    
                    emp.Distance = rnd.Next(0, 101);
    
                }
    
                _count += toGenerate;
    
    
                Debug.WriteLine("wait for 1 second");
    
                await Task.Delay(1000);
    
    
    
                return values.ToArray();
    
            }
    

    3, on page navigatefrom event handler, cacel the task before nagivation.

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    
            {
    
                employees.token.Cancel();
    
            }
    

    Please try to edit to meet your code snippet.

    Regards,

     


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Marked as answer by AvelN Thursday, May 07, 2015 9:43 AM
    Monday, May 04, 2015 5:58 AM
    Moderator

All replies

  • Hi AvelN,

    Thank you for reporting this issue. I can reproduce it using MSDN code sample from here. https://code.msdn.microsoft.com/windowsapps/Data-Binding-7b1d67b5.

    The implementation in this sample has provided how to cancel the Loading approach, you just need to add some code when the main page navigate to others in its OnVavigatedFrom event handler.

    1, In order to do this, we should add a property to public the Cancel Token as following code in IncrementalLoadingBase.cs

    public CancellationTokenSource token = new CancellationTokenSource();
    
    
            public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    
            {
    
                if (_busy)
    
                {
    
                    throw new InvalidOperationException("Only one operation in flight at a time");
    
                }
    
    
                _busy = true;
    
    
                // use a new call method to instead the default one
    
                return LoadMoreItemsAsync(token.Token, count).AsAsyncOperation<LoadMoreItemsResult>();
    
                // default call method
    
               // return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count));
    
            }
    

    2, in GeneratorIncrementalLoadingClass.cs file, call a method to cancel the task.

    protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count)
    
            {
    
                //add this code to cancel the task
    
                c.ThrowIfCancellationRequested();
    
    
    
                uint toGenerate = System.Math.Min(count, _maxCount - _count);
    
                Debug.WriteLine(string.Format("load {0} at {1}", count.ToString(), DateTime.Now.ToString()));
    
                // Wait for work 
    
                await Task.Delay(10);
    
    
                // This code simply generates
    
                var values = from j in Enumerable.Range((int)_count, (int)toGenerate)
    
                             select (object)_generator(j);
    
    
                Random rnd=new Random();
    
                for (int i = 0; i < values.Count(); i++)
    
                {
    
                    Employee emp = values.ElementAt(i) as Employee;
    
                    emp.Distance = rnd.Next(0, 101);
    
                }
    
                _count += toGenerate;
    
    
                Debug.WriteLine("wait for 1 second");
    
                await Task.Delay(1000);
    
    
    
                return values.ToArray();
    
            }
    

    3, on page navigatefrom event handler, cacel the task before nagivation.

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    
            {
    
                employees.token.Cancel();
    
            }
    

    Please try to edit to meet your code snippet.

    Regards,

     


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Marked as answer by AvelN Thursday, May 07, 2015 9:43 AM
    Monday, May 04, 2015 5:58 AM
    Moderator
  • Thanks for your answer, I hope this can be fixed in future versions. I've just found another way, adding additional bool field isStopped with methods Start, ForceStop setting it to false/true. This value is used when getting HasMoreItems like

    bool HasMoreItems{get{return !isStopped && DetermineIfHasMore()};}

    And simply by calling those to methods I can stop or continue loading the same collection generator.

    What do you think about this implementation?

    • Marked as answer by AvelN Thursday, May 07, 2015 9:43 AM
    Thursday, May 07, 2015 9:42 AM