Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset method problem #10

Open
kronic opened this issue Mar 4, 2021 · 9 comments
Open

Reset method problem #10

kronic opened this issue Mar 4, 2021 · 9 comments
Labels

Comments

@kronic
Copy link

kronic commented Mar 4, 2021

#region

using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using BFF.DataVirtualizingCollection.DataVirtualizingCollection;

#endregion

namespace ConsoleApp11
{
	internal static class Program
	{
		public static int Version = 1;
		private const int NumberItem = 5;
		private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
		private static readonly TimeSpan EpsilonDelay = Delay / 10;
		private static TestAsyncEnumerable testAsyncEnumerable;
		private static IDataVirtualizingCollection<int> dataVirtualizingCollection;
		private static async Task Main()
		{
			testAsyncEnumerable = new TestAsyncEnumerable(NumberItem, Delay);

			dataVirtualizingCollection = DataVirtualizingCollectionBuilder.Build<int>
				(
					10,
					new TaskPoolScheduler(Task.Factory)
				)
				.NonPreloading()
				.Hoarding()
				.AsyncEnumerableBasedFetchers(PageFetcher, CountFetcher)
				.AsyncIndexAccess((_, _) => -2);

			await Task.Delay(EpsilonDelay);
			//problem not show CountFetcher cancel
			dataVirtualizingCollection.Reset();
			Version++;
			await Task.Delay(Delay + EpsilonDelay);
			var dataVirtualizing = dataVirtualizingCollection[0];
			await Task.Delay(2 * Delay + EpsilonDelay);
			//problem not show PageFetcher cancel
			Version++;
			dataVirtualizingCollection.Reset();
			//problem not run PageFetcher version 3 
			dataVirtualizing = dataVirtualizingCollection[0];
			Console.WriteLine("End");
			Console.ReadKey();
		}
		private static async Task<int> CountFetcher(CancellationToken cancellationToken)
		{
			var version = Version;
			try
			{
				
				Console.WriteLine($"{nameof(CountFetcher)} begin {nameof(Version)}={version}");
				await Task.Delay(Delay, cancellationToken);
				Console.WriteLine($"{nameof(CountFetcher)} end {nameof(Version)}={version}");

				return NumberItem;
			}
			catch (OperationCanceledException)
			{
				Console.WriteLine($"{nameof(CountFetcher)} cancel {nameof(Version)}={version}");

				throw;
			}
		}
		private static async IAsyncEnumerable<int> PageFetcher
		(
			int offset,
			int count,
			[EnumeratorCancellation] CancellationToken cancellationToken
		)
		{
			var version = Version;
			Console.WriteLine($"{nameof(PageFetcher)} begin {nameof(Version)}={version}");

			await foreach (var item in testAsyncEnumerable.WithCancellation(cancellationToken))
			{
				yield return item;
			}

			Console.WriteLine($"{nameof(PageFetcher)} end {nameof(Version)}={version}");
		}
	}

	public class TestAsyncEnumerable : IAsyncEnumerable<int>
	{
		private readonly int _number;
		private readonly TimeSpan _delay;
		public TestAsyncEnumerable(int number, TimeSpan delay)
		{
			_number = number;
			_delay = delay;
		}
		public IAsyncEnumerator<int> GetAsyncEnumerator
		(
			CancellationToken cancellationToken
		) => new TestAsyncEnumerator(_number, _delay, Program.Version, cancellationToken);
	}

	public class TestAsyncEnumerator : IAsyncEnumerator<int>
	{
		private readonly int _number;
		private readonly TimeSpan _timeSpan;
		private readonly int _version;
		private readonly CancellationToken _cancellationToken;
		public TestAsyncEnumerator(int number, TimeSpan timeSpan, int version, CancellationToken cancellationToken)
		{
			_number = number;
			_timeSpan = timeSpan;
			_version = version;
			_cancellationToken = cancellationToken;
		}
		public int Current { get; private set; } = -1;
		public ValueTask DisposeAsync()
		{
			Console.WriteLine($"{nameof(DisposeAsync)} {nameof(Version)}={_version}");

			return default;
		}
		public async ValueTask<bool> MoveNextAsync()
		{
			try
			{
				Console.WriteLine($"before {nameof(MoveNextAsync)} {nameof(Current)} {Current} {nameof(Version)}={_version}");
				await Task.Delay(_timeSpan, _cancellationToken);
				var current = Current + 1;

				if (current >= _number)
				{
					Console.WriteLine($"end {nameof(MoveNextAsync)} {nameof(Current)} {Current} {nameof(Version)}={_version}");

					return false;
				}

				Current = current;
				Console.WriteLine($"after {nameof(MoveNextAsync)} {nameof(Current)} {Current} {nameof(Version)}={_version}");

				return true;
			}
			catch (OperationCanceledException)
			{
				Console.WriteLine($"{nameof(TestAsyncEnumerator)} is canceled {nameof(Version)}={_version}");

				throw;
			}
		}
	}
}
@kronic
Copy link
Author

kronic commented Mar 4, 2021

Show comment and run code

@Yeah69 Yeah69 added the bug label Mar 7, 2021
Yeah69 added a commit that referenced this issue Mar 7, 2021
@Yeah69
Copy link
Owner

Yeah69 commented Mar 7, 2021

v3.3.4 v3.3.5 should fix the count fetcher cancellation.
I am continuing with the page fetcher.

@Yeah69
Copy link
Owner

Yeah69 commented Mar 14, 2021

Hi again,
I've fixed the cancellation for the count fetcher. Which was indeed broken. And I've adjusted your nice example code in order to show you something:

#region

using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using BFF.DataVirtualizingCollection.DataVirtualizingCollection;

#endregion

namespace ConsoleApp11
{
	internal static class Program
	{
		public static int Version = 1;
		private const int NumberItem = 5;
		private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
		private static readonly TimeSpan EpsilonDelay = Delay / 10;
		private static TestAsyncEnumerable? testAsyncEnumerable;
		private static IDataVirtualizingCollection<int>? dataVirtualizingCollection;
		private static async Task Main()
		{
			testAsyncEnumerable = new TestAsyncEnumerable(NumberItem, Delay);

			dataVirtualizingCollection = DataVirtualizingCollectionBuilder.Build<int>
				(
					10,
					new TaskPoolScheduler(Task.Factory)
				)
				.NonPreloading()
				.Hoarding()
				.AsyncEnumerableBasedFetchers(PageFetcher, CountFetcher)
				.AsyncIndexAccess((_, _) => -2);

			await Task.Delay(EpsilonDelay);
			//problem not show CountFetcher cancel
			Version++;
			dataVirtualizingCollection.Reset();
			await Task.Delay(Delay + EpsilonDelay);
			var dataVirtualizing = dataVirtualizingCollection[0];
			await Task.Delay(EpsilonDelay);
			//problem not show PageFetcher cancel
			Version++;
			dataVirtualizingCollection.Reset();
			//problem not run PageFetcher version 3 
			dataVirtualizing = dataVirtualizingCollection[0];
			Console.WriteLine("End");
			Console.ReadKey();
		}
		private static async Task<int> CountFetcher(CancellationToken cancellationToken)
		{
			var version = Version;
			try
			{
				
				Console.WriteLine($"{nameof(CountFetcher)} begin {nameof(Version)}={version}");
				await Task.Delay(Delay, cancellationToken);
				Console.WriteLine($"{nameof(CountFetcher)} end {nameof(Version)}={version}");

				return NumberItem;
			}
			catch (OperationCanceledException)
			{
				Console.WriteLine($"{nameof(CountFetcher)} cancel {nameof(Version)}={version}");

				throw;
			}
		}
		private static async IAsyncEnumerable<int> PageFetcher
		(
			int offset,
			int count,
			[EnumeratorCancellation] CancellationToken cancellationToken
		)
		{
			var version = Version;
			Console.WriteLine($"{nameof(PageFetcher)} begin {nameof(Version)}={version}");

			await foreach (var item in testAsyncEnumerable!.WithCancellation(cancellationToken))
			{
				yield return item;
			}

			Console.WriteLine($"{nameof(PageFetcher)} end {nameof(Version)}={version}");
		}
	}

	public class TestAsyncEnumerable : IAsyncEnumerable<int>
	{
		private readonly int _number;
		private readonly TimeSpan _delay;
		public TestAsyncEnumerable(int number, TimeSpan delay)
		{
			_number = number;
			_delay = delay;
		}
		public IAsyncEnumerator<int> GetAsyncEnumerator
		(
			CancellationToken cancellationToken
		) => new TestAsyncEnumerator(_number, _delay, Program.Version, cancellationToken);
	}

	public class TestAsyncEnumerator : IAsyncEnumerator<int>
	{
		private readonly int _number;
		private readonly TimeSpan _timeSpan;
		private readonly int _version;
		private readonly CancellationToken _cancellationToken;
		public TestAsyncEnumerator(int number, TimeSpan timeSpan, int version, CancellationToken cancellationToken)
		{
			_number = number;
			_timeSpan = timeSpan;
			_version = version;
			_cancellationToken = cancellationToken;
		}
		public int Current { get; private set; } = -1;
		public ValueTask DisposeAsync()
		{
			Console.WriteLine($"{nameof(DisposeAsync)} {nameof(Version)}={_version}");

			return default;
		}
		public async ValueTask<bool> MoveNextAsync()
		{
			try
			{
				Console.WriteLine($"before {nameof(MoveNextAsync)} {nameof(Current)} {Current} {nameof(Version)}={_version}");
				await Task.Delay(_timeSpan, _cancellationToken);
				var current = Current + 1;

				if (current >= _number)
				{
					Console.WriteLine($"end {nameof(MoveNextAsync)} {nameof(Current)} {Current} {nameof(Version)}={_version}");

					return false;
				}

				Current = current;
				Console.WriteLine($"after {nameof(MoveNextAsync)} {nameof(Current)} {Current} {nameof(Version)}={_version}");

				return true;
			}
			catch (OperationCanceledException)
			{
				Console.WriteLine($"{nameof(TestAsyncEnumerator)} is canceled {nameof(Version)}={_version}");

				throw;
			}
		}
	}
}

The two minor changes that I have done is: First, I put the first Version incrementation before the reset. The reason for that is that during the Reset call the count fetcher is triggered. Hence, if the Version isn't increased before, it would still log the previous Version. Second, I've removed the 2 * Delay + from the third Delay, so it just delays by Epsilon.
This is what I get in the logs:

CountFetcher begin Version=1
CountFetcher cancel Version=1
CountFetcher begin Version=2
CountFetcher end Version=2
PageFetcher begin Version=2
before MoveNextAsync Current -1 Version=2
CountFetcher begin Version=3
End
after MoveNextAsync Current 0 Version=2
before MoveNextAsync Current 0 Version=2
CountFetcher end Version=3
TestAsyncEnumerator is canceled Version=2
DisposeAsync Version=2

So the count fetcher fix seems to work.

For the second problem I didn't change anything in BFF.DVC. With a smaller Delay it seems to work as intended. You can see that the TestAsyncEnumerator started and fetched the first item and than got canceled before getting to fetch the next item. I assume that the delay before my adjustment was so big that the enumerator finished before the cancelation. If that is the case than of course there would be nothing to cancel.

As for the third problem. This is intended by design of BFF.DVC. In order for the page fetcher to run again after a Reset call you would need to request one of the indices of that page again. In BFF.DVC the Reset doesn't mean that the pages that were loaded before the Reset will be requested again. If it would do that, this would imply that for a hoarding virtualized collection where everything was request before (for example by scrolling through the whole list) after a Reset the whole collection would be requested at once. So nothing would be virtualized. The Reset call is just the way for the user to say anything that you (BFF.DVC) have fetched until now could be invalid, because something in the backend changed. BFF.DVC will then throw everything away and signal through INotifyCollectionChanged that the whole collection changed (the enum-Value of the event is also called the Reset I think). The idea is, if you scrolled to the middle of the list and a Reset got triggered, then you just need to fetch the pages in the middle which are presented currently and not the pages which are already scrolled through but not shown anymore.

Did the fix and the explanations help you?

@kronic
Copy link
Author

kronic commented Mar 15, 2021

@Yeah69 Works as expected, thanks

@kronic
Copy link
Author

kronic commented Mar 15, 2021

@Yeah69 In wpf, after calling the reset method, sometimes display records from placeholder

@kronic
Copy link
Author

kronic commented Mar 17, 2021

System.InvalidOperationException
HResult=0x80131509
Сообщение = Добавленный элемент не отображается в заданном индексе "3".
Источник = PresentationFramework
Трассировка стека:
в System.Windows.Data.ListCollectionView.AdjustBefore(NotifyCollectionChangedAction action, Object item, Int32 index)

Изначально это исключение было создано в этом стеке вызовов:
System.Windows.Data.ListCollectionView.AdjustBefore(System.Collections.Specialized.NotifyCollectionChangedAction, object, int)

PresentationFramework.dll!System.Windows.Data.ListCollectionView.AdjustBefore(System.Collections.Specialized.NotifyCollectionChangedAction action, object item, int index)	Нет данных
PresentationFramework.dll!System.Windows.Data.ListCollectionView.ProcessCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs args)	Нет данных
PresentationFramework.dll!System.Windows.Data.CollectionView.OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs args)	Нет данных
BFF.DataVirtualizingCollection.dll!BFF.DataVirtualizingCollection.VirtualizationBase<CommunicationInfrastructure.Models.DetailModel>.OnCollectionChangedReplace(CommunicationInfrastructure.Models.DetailModel newItem, CommunicationInfrastructure.Models.DetailModel oldItem, int index)	Нет данных
BFF.DataVirtualizingCollection.dll!BFF.DataVirtualizingCollection.DataVirtualizingCollection.DataVirtualizingCollectionBase<CommunicationInfrastructure.Models.DetailModel>..ctor.AnonymousMethod__1_0((int Offset, int PageSize, CommunicationInfrastructure.Models.DetailModel[] PreviousPage, CommunicationInfrastructure.Models.DetailModel[] Page) t)	Нет данных

System.Reactive.dll!System.Reactive.AnonymousSafeObserver<(int, int, CommunicationInfrastructure.Models.DetailModel[], CommunicationInfrastructure.Models.DetailModel[])>.OnNext((int, int, CommunicationInfrastructure.Models.DetailModel[], CommunicationInfrastructure.Models.DetailModel[]) value) Строка 44 C#
System.Reactive.dll!System.Reactive.Sink<(int, int, CommunicationInfrastructure.Models.DetailModel[], CommunicationInfrastructure.Models.DetailModel[])>.ForwardOnNext((int, int, CommunicationInfrastructure.Models.DetailModel[], CommunicationInfrastructure.Models.DetailModel[]) value) Строка 50 C#
System.Reactive.dll!System.Reactive.ObserveOnObserverNew<(int, int, CommunicationInfrastructure.Models.DetailModel[], CommunicationInfrastructure.Models.DetailModel[])>.DrainStep(System.Collections.Concurrent.ConcurrentQueue<(int, int, CommunicationInfrastructure.Models.DetailModel[], CommunicationInfrastructure.Models.DetailModel[])> q) Строка 573 C#
System.Reactive.dll!System.Reactive.ObserveOnObserverNew<(int, int, CommunicationInfrastructure.Models.DetailModel[], CommunicationInfrastructure.Models.DetailModel[])>.DrainShortRunning(System.Reactive.Concurrency.IScheduler recursiveScheduler) Строка 509 C#
System.Reactive.dll!System.Reactive.ObserveOnObserverNew<(int, int, System.__Canon, System.__Canon)>..cctor.AnonymousMethod__17_0(System.Reactive.Concurrency.IScheduler scheduler, System.Reactive.ObserveOnObserverNew<(int, int, System.__Canon, System.__Canon)> self) Строка 497 C#
System.Reactive.dll!System.Reactive.Concurrency.SynchronizationContextScheduler.Schedule.AnonymousMethod__0() Строка 67 C#
System.Reactive.dll!System.Reactive.Concurrency.SynchronizationContextExtensions.PostWithStartComplete.AnonymousMethod__0(object _) Строка 40 C#
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) Нет данных
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) Нет данных
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() Нет данных
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state) Нет данных
WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(object obj) Нет данных
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Нет данных
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Нет данных
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Нет данных
WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Нет данных
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() Нет данных
WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() Нет данных
WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) Нет данных
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) Нет данных
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) Нет данных
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) Нет данных
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) Нет данных
WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) Нет данных
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) Нет данных
[Переход от машинного кода к управляемому]
[Переход от управляемого кода к машинному]
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) Нет данных
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) Нет данных
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) Нет данных
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) Нет данных
PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) Нет данных
PresentationFramework.dll!System.Windows.Application.Run() Нет данных
ShowErrors.exe!ShowErrors.App.Main() Нет данных

@kronic
Copy link
Author

kronic commented Mar 17, 2021

image

@kronic
Copy link
Author

kronic commented Mar 18, 2021

image

@Yeah69
Copy link
Owner

Yeah69 commented Apr 4, 2021

I am sorry. I don't know where to begin to resolve the issue based on the call stacks you posted. Honestly, I have no idea how to reproduce it.
Does it happen after a sequence of specific actions? Or does it occur just randomly out of nowhere?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants