public void HandlesCancellation() { var asyncLock = new AsyncLock(); var lockOne = asyncLock.LockAsync(); var cts = new CancellationTokenSource(); var lockTwo = asyncLock.LockAsync(cts.Token); Assert.True(lockOne.IsCompleted); Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status); Assert.NotNull(lockOne.Result); Assert.False(lockTwo.IsCompleted); cts.Cancel(); Assert.True(lockTwo.IsCompleted); Assert.True(lockTwo.IsCanceled); lockOne.Result.Dispose(); }
/// <summary> /// Starts the operation queue. /// </summary> /// <returns>A disposable which when Disposed will stop the queue.</returns> public IDisposable Start() { if (_start is not null) { return(_start); } _shouldQuit = new CancellationTokenSource(); var task = Task.Run(async() => { var toProcess = new List <OperationQueueItem>(); while (!_shouldQuit.IsCancellationRequested) { toProcess.Clear(); IDisposable? @lock; try { @lock = await _flushLock.LockAsync(_shouldQuit.Token).ConfigureAwait(false); } catch (OperationCanceledException) { break; } // Verify lock was acquired if (@lock is null) { break; } using (@lock) { // NB: We special-case the first item because we want to // in the empty list case, we want to wait until we have an item. // Once we have a single item, we try to fetch as many as possible // until we've got enough items. OperationQueueItem?item; try { item = _operationQueue.Take(_shouldQuit.Token); } catch (OperationCanceledException) { break; } // NB: We explicitly want to bail out *here* because we // never want to bail out in the middle of processing // operations, to guarantee that we won't orphan them if (_shouldQuit.IsCancellationRequested && item is null) { break; } toProcess.Add(item); while (toProcess.Count < Constants.OperationQueueChunkSize && _operationQueue.TryTake(out item) && item is not null) { toProcess.Add(item); } try { ProcessItems(CoalesceOperations(toProcess)); } catch (SQLiteException) { // NB: If ProcessItems Failed, it explicitly means // that the "BEGIN TRANSACTION" failed and that items // have **not** been processed. We should add them back // to the queue foreach (var v in toProcess) { _operationQueue.Add(v); } } } } }); return(_start = Disposable.Create( () => { try { _shouldQuit.Cancel(); task.Wait(); } catch (OperationCanceledException) { } try { using (_flushLock.LockAsync().Result) { FlushInternal(); } } catch (OperationCanceledException) { } _start = null; })); }
public IDisposable Start() { if (start != null) { return(start); } shouldQuit = new CancellationTokenSource(); var task = new Task(async() => { var toProcess = new List <OperationQueueItem>(); while (!shouldQuit.IsCancellationRequested) { toProcess.Clear(); using (await flushLock.LockAsync(shouldQuit.Token)) { // NB: We special-case the first item because we want to // in the empty list case, we want to wait until we have an item. // Once we have a single item, we try to fetch as many as possible // until we've got enough items. var item = default(OperationQueueItem); try { item = operationQueue.Take(shouldQuit.Token); } catch (OperationCanceledException) { break; } // NB: We explicitly want to bail out *here* because we // never want to bail out in the middle of processing // operations, to guarantee that we won't orphan them if (shouldQuit.IsCancellationRequested && item == null) { break; } toProcess.Add(item); while (toProcess.Count < Constants.OperationQueueChunkSize && operationQueue.TryTake(out item)) { toProcess.Add(item); } try { ProcessItems(CoalesceOperations(toProcess)); } catch (SQLiteException) { // NB: If ProcessItems Failed, it explicitly means // that the "BEGIN TRANSACTION" failed and that items // have **not** been processed. We should add them back // to the queue foreach (var v in toProcess) { operationQueue.Add(v); } } } } }, TaskCreationOptions.LongRunning); task.Start(); return(start = Disposable.Create(() => { try { shouldQuit.Cancel(); task.Wait(); } catch (OperationCanceledException) { } var newQueue = new BlockingCollection <OperationQueueItem>(); ProcessItems(CoalesceOperations(Interlocked.Exchange(ref operationQueue, newQueue).ToList())); start = null; })); }