public void Decrease_disallowed()
        {
            // Arrange
            var semaphore = new SemaphoreSlimEx(1, 1, 2);

            // Act
            var decreased = semaphore.TryDecrease();

            // Assert
            Assert.IsFalse(decreased);
            Assert.AreEqual(1, semaphore.AvailableSlotsCount);
        }
        public void Increase_allowed()
        {
            // Arrange
            var semaphore = new SemaphoreSlimEx(1, 1, 2);

            // Act
            var increased = semaphore.TryIncrease();

            // Assert
            Assert.IsTrue(increased);
            Assert.AreEqual(2, semaphore.AvailableSlotsCount);
        }
        private async Task ProcessMessages(TimeSpan?visibilityTimeout = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            var runningTasks = new ConcurrentDictionary <Task, Task>();
            var semaphore    = new SemaphoreSlimEx(_minConcurrentTasks, _minConcurrentTasks, _maxConcurrentTasks);

            // Define the task pump
            var pumpTask = Task.Run(async() =>
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

                    var runningTask = Task.Run(async() =>
                    {
                        if (cancellationToken.IsCancellationRequested)
                        {
                            return(false);
                        }

                        CloudQueueMessage message = null;
                        try
                        {
                            message = await _cloudQueue.GetMessageAsync(visibilityTimeout, null, null, cancellationToken);
                        }
                        catch (Exception e)
                        {
                            _logger.ErrorException("An error occured when attempting to get a message from the queue", e);
                        }

                        if (message == null)
                        {
                            try
                            {
                                // The queue is empty
                                OnQueueEmpty?.Invoke(cancellationToken);
                            }
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
                            catch
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
                            {
                                // Intentionally left empty. We ignore errors from OnQueueEmpty.
                            }

                            // False indicates that no message was processed
                            return(false);
                        }
                        else
                        {
                            try
                            {
                                // Process the message
                                OnMessage?.Invoke(message, cancellationToken);

                                // Delete the processed message from the queue
                                await _cloudQueue.DeleteMessageAsync(message);
                            }
                            catch (Exception ex)
                            {
                                var isPoison = (message.DequeueCount > _maxDequeueCount);
                                OnError?.Invoke(message, ex, isPoison);
                                if (isPoison)
                                {
                                    await _cloudQueue.DeleteMessageAsync(message);
                                }
                            }

                            // True indicates that a message was processed
                            return(true);
                        }
                    }, CancellationToken.None);

                    runningTasks.TryAdd(runningTask, runningTask);

                    runningTask.ContinueWith(async t =>
                    {
                        // Decide if we need to scale up or down
                        if (!cancellationToken.IsCancellationRequested)
                        {
                            if (await t)
                            {
                                // The queue is not empty, therefore increase the number of concurrent tasks
                                semaphore.TryIncrease();
                            }
                            else
                            {
                                // The queue is empty, therefore reduce the number of concurrent tasks
                                semaphore.TryDecrease();
                            }
                        }

                        // Complete the task
                        semaphore.Release();
                        Task taskToBeRemoved;
                        runningTasks.TryRemove(t, out taskToBeRemoved);
                    }, TaskContinuationOptions.ExecuteSynchronously)
                    .IgnoreAwait();
                }
            });

            // Run the task pump until canceled
            await pumpTask.UntilCancelled().ConfigureAwait(false);

            // Task pump has been canceled, wait for the currently running tasks to complete
            await Task.WhenAll(runningTasks.Values).UntilCancelled().ConfigureAwait(false);
        }