Reputation: 651
I have a function that refreshes the UI every time an object raises an event. However, any single object can raise multiple events within 1 sec, and there could be 10,000+ objects in the collection doing this. My idea is to capture the very last event and discard any pending ones after 1 sec intervals.
The following RefreshCollection()
function is called every time any object raises any event.
SemaphoreSlim _semaphoreUpdatingList = new SemaphoreSlim(1);
SemaphoreSlim _semaphoreRefreshingView = new SemaphoreSlim(1);
CancellationTokenSource _ctsRefreshView = null;
internal void RefreshCollection()
{
// if we're in the process of changing the collection, return
if (_semaphoreUpdatingList.CurrentCount == 0)
{
return;
}
if (_ctsRefreshView != null)
{
_ctsRefreshView.Cancel();
}
Task.Run(async () =>
{
if (_ctsRefreshView == null)
{
_ctsRefreshView = new CancellationTokenSource();
}
var ct = _ctsRefreshView.Token;
try
{
await _semaphoreRefreshingView.WaitAsync(ct);
var stopWatch = new Stopwatch();
stopWatch.Start();
Application.Current?.Dispatcher?.Invoke(() =>
{
CollectionView.Refresh();
});
stopWatch.Stop();
// Only refresh every 1 sec
if (stopWatch.ElapsedMilliseconds < 1000)
{
await Task.Delay(1000 - (int)stopWatch.ElapsedMilliseconds);
}
_semaphoreRefreshingView.Release();
}
catch (OperationCanceledException)
{
return;
}
finally
{
_ctsRefreshView = null;
}
});
}
The problem is, very rarely I am getting a _ctsRefreshView is null
error inside the task when I'm calling this var ct = _ctsRefreshView.Token;
. I am scratching my head as to why this is happening.
Thanks a lot for any help.
Upvotes: 0
Views: 106
Reputation: 117019
You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive.Windows.Threading
(for WPF bits) and add using System.Reactive.Linq;
.
And if you had a collection
this class:
public class MyObject
{
public event EventHandler Ping;
}
Then you can do this:
IObservable<EventPattern<EventArgs>> query =
collection
.ToObservable()
.SelectMany(x =>
Observable
.FromEventPattern<EventHandler, EventArgs>(
h => x.Ping += h,
h => x.Ping -= h))
.Sample(TimeSpan.FromSeconds(0.1))
.ObserveOnDispatcher();
IDisposable subscription = query.Subscribe(x => CollectionView.Refresh());
That will give you at most one call to CollectionView.Refresh()
every 0.1
seconds.
That's much easier than mucking around with cancellation token sources.
And just call subscription.Dispose();
to stop it all.
Upvotes: 1