public void Initialize_WhenCalledMultipleTimesConcurrently_ExecutesOnce() { var executionCount = 0; var config = CachedObjectFactory .ConfigureFor(GetValue) .WithRefreshInterval(TimeSpan.FromMinutes(1)); using var cachedObject = BuildCachedObject(config); var tasks = Enumerable .Range(0, 10) .Select(_ => Task.Run(() => cachedObject.InitializeAsync())) .ToArray(); Task.WaitAll(tasks); executionCount.Should().Be(1); async Task <DateTime> GetValue() { await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false); Interlocked.Increment(ref executionCount); return(DateTime.UtcNow); } }
public async Task InitializationIsDeferredUntilRequired() { using var source = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow) .Build(); using var mapped = source.Map(x => DateTime.UtcNow); DateTime dateOfSourceInitialization = default; DateTime dateOfDestinationInitialization = default; source.OnInitialized += (_, __) => dateOfSourceInitialization = DateTime.UtcNow; mapped.OnInitialized += (_, __) => dateOfDestinationInitialization = DateTime.UtcNow; await source.InitializeAsync().ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); await mapped.InitializeAsync().ConfigureAwait(false); source.Value.Should().BeCloseTo(dateOfSourceInitialization, TimeSpan.FromMilliseconds(100)); mapped.Value.Should().BeCloseTo(dateOfDestinationInitialization, TimeSpan.FromMilliseconds(100)); var diff = mapped.Value - source.Value; diff.Should().BeCloseTo(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(100)); }
public void UpdateableCachedObject_WithRefreshSchedule_RefreshesValueAtExpectedTimes(string cronExpression, int intervalSeconds) { var countdown = new CountdownEvent(3); var refreshDates = new List <DateTime>(); var cachedObject = CachedObjectFactory .ConfigureFor(async() => { var start = DateTime.UtcNow; await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false); return(start); }) .WithUpdates <bool>((_, __) => DateTime.UtcNow) .WithRefreshSchedule(cronExpression, true) .OnValueRefresh(r => { if (r.IsResultOfInitialization) { return; } refreshDates.Add(r.NewValue); if (!countdown.IsSet) { countdown.Signal(); } }) .Build(); RunTest(cachedObject, countdown, refreshDates, intervalSeconds); }
public void ValueIsUpdatedEachTimeSourceIsUpdated(bool async) { using var source = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow.Ticks) .WithUpdates <bool>((_, __) => DateTime.UtcNow.Ticks) .Build(); using var signal = new AutoResetEvent(false); using var mapped = async ? source.MapAsync(x => Task.Delay(TimeSpan.FromMilliseconds(20)).ContinueWith(_ => - x)) : source.Map(x => - x); mapped.Initialize(); mapped.OnValueRefreshed += (_, __) => signal.Set(); for (var i = 0; i < 10; i++) { source.UpdateValue(true); signal.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue(); mapped.Version.Should().Be(i + 2); mapped.Value.Should().Be(-source.Value); } }
public void FunctionProvidedToMapSourceUpdates_ValueIsUpdatedEachTimeSourceIsUpdated(bool async) { using var source = CachedObjectFactory .ConfigureFor(() => 1) .WithUpdates <int>((current, updates) => current + updates) .Build(); using var signal = new AutoResetEvent(false); using var mapped = async ? source.MapAsync(x => Task.Delay(TimeSpan.FromMilliseconds(20)).ContinueWith(_ => - x), (current, sourceValue, updates) => Task.FromResult(current + updates)) : source.Map(x => - x, (current, sourceValue, updates) => current + updates); mapped.Initialize(); mapped.OnValueUpdated += (_, __) => signal.Set(); for (var i = 0; i < 10; i++) { source.UpdateValue(i); signal.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue(); mapped.Version.Should().Be(i + 2); mapped.Value.Should().Be(source.Value - 2); } }
public void DisposePropagatesToAllChildren() { using var source = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow) .Build(); var cachedObjects = new ICachedObject <DateTime> [10]; var previous = source; for (var i = 0; i < 10; i++) { var current = previous.Map(x => x.AddDays(1)); cachedObjects[i] = current; previous = current; } cachedObjects.Last().Initialize(); foreach (var cachedObject in cachedObjects) { cachedObject.State.Should().Be(CachedObjectState.Ready); } source.Dispose(); foreach (var cachedObject in cachedObjects) { cachedObject.State.Should().Be(CachedObjectState.Disposed); } }
public void MappedCachedObjectsCanBeChained() { using var source = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow) .Build(); var cachedObjects = new ICachedObject <DateTime> [10]; var previous = source; for (var i = 0; i < 10; i++) { var current = previous.Map(x => x.AddDays(1)); cachedObjects[i] = current; previous = current; } foreach (var cachedObject in cachedObjects) { cachedObject.State.Should().Be(CachedObjectState.PendingInitialization); } cachedObjects.Last().Initialize(); foreach (var cachedObject in cachedObjects) { cachedObject.State.Should().Be(CachedObjectState.Ready); } for (var i = 0; i < 10; i++) { cachedObjects[i].Value.Should().Be(source.Value.AddDays(i + 1)); } }
public void WithUpdateSchedule_UpdatesValueAtExpectedTimes(string cronExpression, int intervalSeconds) { var countdown = new CountdownEvent(3); var updateDates = new List <DateTime>(); var cachedObject = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow) .WithUpdatesAsync(async _ => { var start = DateTime.UtcNow; await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false); return(start); }, (current, next) => Task.FromResult(next)) .WithUpdateSchedule(cronExpression, true) .OnValueUpdate(e => { updateDates.Add(e.NewValue); if (!countdown.IsSet) { countdown.Signal(); } }) .Build(); RunTest(cachedObject, countdown, updateDates, intervalSeconds); }
public void Initialize_IfFails_CanBeCalledAgain() { var count = 0; var config = CachedObjectFactory .ConfigureFor(GetValue) .WithRefreshInterval(TimeSpan.FromSeconds(1)); using var cachedObject = BuildCachedObject(config); Action action = () => cachedObject.Initialize(); for (var i = 0; i < 5; i++) { action.Should().Throw <Exception>(); } cachedObject.State.Should().Be(CachedObjectState.PendingInitialization); action.Should().NotThrow(); cachedObject.State.Should().Be(CachedObjectState.Ready); DateTime GetValue() { if (count++ < 5) { throw new Exception(); } return(DateTime.UtcNow); } }
public void ValueIsMappedFromSourceCorrectly(bool async) { using var source = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow.Ticks) .Build(); using var mapped = async ? source.MapAsync(x => Task.Delay(TimeSpan.FromMilliseconds(20)).ContinueWith(_ => - x)) : source.Map(x => - x); mapped.Value.Should().Be(-source.Value); }
public void Value_IfCalledWhenNotInitialized_WillInitializeValue() { var config = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow) .WithRefreshInterval(TimeSpan.FromMinutes(1)); using var cachedObject = BuildCachedObject(config); cachedObject.State.Should().Be(CachedObjectState.PendingInitialization); var value = cachedObject.Value; cachedObject.State.Should().Be(CachedObjectState.Ready); value.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(100)); }
public void WithUpdateFunc_ValueIsUpdatedCorrectly() { using var cachedObject = CachedObjectFactory .ConfigureFor(() => 1) .WithUpdates(_ => 1, (current, updates) => current + updates) .Build(); cachedObject.Value.Should().Be(1); for (var i = 2; i < 100; i++) { cachedObject.UpdateValue(); cachedObject.Value.Should().Be(i); cachedObject.Version.Should().Be(i); } }
public async Task UpdateAsync_RefreshValueAsync_UnderlyingTasksAlwaysRunOneAtATime() { var concurrentExecutions = 0; var cachedObject = CachedObjectFactory .ConfigureFor(RefreshValue) .WithUpdatesAsync(_ => Task.FromResult(1), UpdateValue) .Build(); cachedObject.Initialize(); var tasks = Enumerable .Range(0, 10) .SelectMany(_ => new[] { cachedObject.UpdateValueAsync(), cachedObject.RefreshValueAsync() }) .ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); async Task <int> RefreshValue() { if (Interlocked.Increment(ref concurrentExecutions) > 1) { throw new Exception(); } await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false); Interlocked.Decrement(ref concurrentExecutions); return(0); } async Task <int> UpdateValue(int current, int input) { if (Interlocked.Increment(ref concurrentExecutions) > 1) { throw new Exception(); } await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false); Interlocked.Decrement(ref concurrentExecutions); return(current + input); } }
public async Task UpdateAsync_RefreshValueAsync_RefreshesTakePriority() { var lockObj = new object(); var cachedObject = CachedObjectFactory .ConfigureFor(RefreshValue) .WithUpdatesAsync(_ => Task.FromResult(1), UpdateValue) .Build(); cachedObject.Initialize(); var tasksInOrderOfCompletion = new List <(Task Task, bool IsRefresh)>(); var updates = Enumerable.Range(0, 10).Select(_ => RunTask(cachedObject.UpdateValueAsync(), false)).ToList(); var refreshes = Enumerable.Range(0, 10).Select(_ => RunTask(cachedObject.RefreshValueAsync(), true)).ToList(); await Task.WhenAll(updates.Concat(refreshes)).ConfigureAwait(false); for (var i = 0; i < 20; i++) { if (i == 0) { tasksInOrderOfCompletion[i].IsRefresh.Should().BeFalse(); } else if (i <= 10) { tasksInOrderOfCompletion[i].IsRefresh.Should().BeTrue(); } else { tasksInOrderOfCompletion[i].IsRefresh.Should().BeFalse(); } } Task RunTask(Task task, bool isRefresh) { return(task.ContinueWith(t => { lock (lockObj) tasksInOrderOfCompletion.Add((t, isRefresh)); })); }
public async Task Status_UpdatedCorrectly() { var config = CachedObjectFactory .ConfigureFor(GetValue) .WithRefreshInterval(TimeSpan.FromMinutes(1)); using var cachedObject = BuildCachedObject(config); cachedObject.State.Should().Be(CachedObjectState.PendingInitialization); var task = cachedObject.InitializeAsync(); cachedObject.State.Should().Be(CachedObjectState.InitializationInProgress); await task.ConfigureAwait(false); cachedObject.State.Should().Be(CachedObjectState.Ready); cachedObject.Dispose(); cachedObject.State.Should().Be(CachedObjectState.Disposed);
public void IfMappingFunctionThrowsException_LaterUpdatesAreStillProcessed() { using var source = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow) .Build(); var count = 0; using var mapped = source.Map(x => { if (count++ % 2 == 1) { throw new Exception(); } return(x); }); using var signal = new AutoResetEvent(false); mapped.Initialize(); mapped.OnValueRefreshed += (_, __) => signal.Set(); mapped.OnValueRefreshException += (_, __) => signal.Set(); for (var i = 0; i < 10; i++) { source.RefreshValue(); signal.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue(); if (i % 2 == 0) { mapped.Value.Should().BeBefore(source.Value); } else { mapped.Value.Should().Be(source.Value); } } }
public async Task MultipleConcurrentCallsToInitialize_MappingFunctionCalledOnce() { using var source = CachedObjectFactory .ConfigureFor(() => DateTime.UtcNow) .Build(); var executions = 0; using var mapped = source.Map(x => { Interlocked.Increment(ref executions); Thread.Sleep(TimeSpan.FromSeconds(1)); return(x); }); var tasks = Enumerable .Range(0, 10).Select(_ => Task.Run(() => mapped.InitializeAsync())) .ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); executions.Should().Be(1); }
public void OnceInitialized_ValueIsRefreshedAtRegularIntervals() { var values = new List <DateTime>(); var countdown = new CountdownEvent(5); var config = CachedObjectFactory .ConfigureFor(GetValue) .WithRefreshInterval(TimeSpan.FromSeconds(1)); using var cachedObject = BuildCachedObject(config); cachedObject.Initialize(); countdown.Wait(); for (var i = 1; i < 5; i++) { var diff = values[i] - values[i - 1]; diff.Should().BeGreaterThan(TimeSpan.FromSeconds(0.5)).And.BeLessThan(TimeSpan.FromSeconds(1.5)); } DateTime GetValue() { var now = DateTime.UtcNow; values.Add(now); if (!countdown.IsSet) { countdown.Signal(); } return(now); } }
/// <summary> /// コンストラクタで、ITextureCachedObject派生クラスを生成するfactoryを渡してやる。 /// /// 例) /// TextureLoader loader = new TextureLoader(delegate { /// return new GlTexture(); }); /// </summary> /// <remarks> /// ディフォルトではcache size = 64MB /// OpenGLを描画に使っている場合、テクスチャサイズは2のべき乗でalignされる。 /// たとえば、640×480の画像ならば1024×512(32bpp)になる。 /// よって、1024×512×4byte = 2097152 ≒ 2MB消費する。 /// 50MBだと640×480の画像をおおよそ25枚読み込めると考えて良いだろう。 /// </remarks> /// <param name="factory"></param> public TextureLoader(CachedObjectFactory factory) { Factory = factory; }