private void ThrowIfInvalidToken(BatchAggregatorToken <TBatchItem> token) { if (token.Disposed) { throw new ObjectDisposedException("token", "The aggregator token was already disposed"); } if (!_currentBatchAggregators.Contains(token)) { throw new InvalidOperationException("The aggregator is not part of the current batch"); } }
public bool Equals(BatchAggregatorToken <TBatchItem> other) { if (ReferenceEquals(null, other)) { return(false); } if (ReferenceEquals(this, other)) { return(true); } return(Id.Equals(other.Id)); }
/// <summary> /// Several concurrent callers can contribute to the batch each one of them must hold a token /// </summary> /// <returns></returns> public async Task <BatchAggregatorToken <TBatchItem> > NewBatchAggregatorToken() { var token = new BatchAggregatorToken <TBatchItem>(this); if (!await _allowItemsEvent.WaitAsync(_cts.Token)) { throw new TaskCanceledException(); } lock (_syncLock) { _currentBatchAggregators.Add(token); } _lastOperation = Guid.NewGuid(); return(token); }
public async Task Add(TBatchItem item, BatchAggregatorToken <TBatchItem> token, bool willAddMoreItemsWithThisToken = true) { ThrowIfInvalidToken(token); if (!await _allowItemsEvent.WaitAsync(_cts.Token)) { throw new TaskCanceledException(); } _items.Add(item); _lastOperation = Guid.NewGuid(); if (!willAddMoreItemsWithThisToken) { await CompleteChunk(token, false); } else if (ChunkSize > 0 && _items.Count >= ChunkSize) { await CompleteChunk(token, true); } }
private async Task CompleteChunk(BatchAggregatorToken <TBatchItem> token, bool chunkLimitReachedTriggers) { await AwaitEnlistersUntilNoMoreEnlist(); Task awaitForTask; var itemsToProcess = new TBatchItem[0]; lock (_syncLock) { ThrowIfInvalidToken(token); if (!chunkLimitReachedTriggers) { _currentBatchAggregators.Remove(token); } var executeBatch = chunkLimitReachedTriggers || !_currentBatchAggregators.Any(); if (executeBatch) { _allowItemsEvent.Reset(); Status = BatchStatus.Executing; var batchItems = _items.ToArray(); var itemsToKeep = new TBatchItem[0]; if (ChunkSize == 0) { itemsToProcess = batchItems; } else { itemsToProcess = batchItems.Take(ChunkSize).ToArray(); itemsToKeep = batchItems.Skip(ChunkSize).ToArray(); } awaitForTask = RunChunk(); //immediately allow new additions _items = new ConcurrentBag <TBatchItem>(itemsToKeep); Status = BatchStatus.Opened; _allowItemsEvent.Set(); _resetEvent.Set(); _resetEvent.Reset(); } else { awaitForTask = _resetEvent.WaitAsync(_cts.Token); } } await awaitForTask; async Task AwaitEnlistersUntilNoMoreEnlist() { var current = _lastOperation; //by doing this we are letting other asynchronous tasks to enlist before executing the batch await Task.Delay(EnlistAwaitTimeout); while (current != _lastOperation) { current = _lastOperation; await Task.Delay(EnlistAwaitTimeout); } } Task RunChunk() { try { return(_batchChunkProcessor.Process(itemsToProcess, _cts.Token)); } catch (Exception ex) { ErrorResult errorResult; int attempts = 0; do { errorResult = _batchChunkProcessor.HandleError(itemsToProcess, ex, ++attempts); } while (errorResult == ErrorResult.Retry); if (errorResult == ErrorResult.AbortAndRethrow) { throw; } return(Task.CompletedTask); ; } } }
public async Task AddingItemsToBatchCompleted(BatchAggregatorToken <TBatchItem> token) { await CompleteChunk(token, false); }