/// <summary>
        /// Handles the specified message.
        /// </summary>
        /// <typeparam name="T">the type of the message.</typeparam>
        /// <param name="workGroup">The work group.</param>
        /// <param name="message">The message.</param>
        /// <param name="notifications">The notifications.</param>
        /// <param name="functionToRun">The function to run.</param>
        /// <param name="taskFactory">The task factory.</param>
        /// <returns></returns>
        public Task HandleAsync <T>(IWorkGroup workGroup, IReceivedMessage <T> message, IWorkerNotification notifications, Action <IReceivedMessage <T>, IWorkerNotification> functionToRun, ITaskFactory taskFactory)
            where T : class
        {
            Guard.NotNull(() => message, message);
            Guard.NotNull(() => notifications, notifications);
            Guard.NotNull(() => functionToRun, functionToRun);
            Guard.NotNull(() => taskFactory, taskFactory);

            while (true)
            {
                //verify that we are not canceling or stopping before trying to queue the item
                //however, the transport must support rollbacks
                if (!ShouldHandle(notifications))
                {
                    return(null);
                }

                if (taskFactory.TryStartNew(state => { WrappedFunction(message, notifications, functionToRun); }, new StateInformation(workGroup), task =>
                {
                    if (task.IsFaulted && task.Exception?.InnerException is OperationCanceledException)
                    {
                        //bubble the cancel exception; the queue will rollback the message if possible
                        throw new OperationCanceledException("user canceled", task.Exception.InnerException); //explicitly throw this
                    }

                    if (task.IsFaulted && task.Exception != null)
                    {
                        //need to throw it
                        throw new DotNetWorkQueueException("Message processing exception", task.Exception.InnerException);
                    }
                }, out var start).Success())
                {
                    try
                    {
                        return(start);
                    }
                    finally
                    {
                        //block here if the scheduler is full
                        try
                        {
                            _waitingOnFreeThreadCounter.Increment();
                            taskFactory.Scheduler.WaitForFreeThread.Wait(workGroup);
                        }
                        finally
                        {
                            _waitingOnFreeThreadCounter.Decrement();
                        }
                    }
                }

                //block if the scheduler is full
                try
                {
                    _waitingOnFreeThreadCounter.Increment();
                    taskFactory.Scheduler.WaitForFreeThread.Wait(workGroup);
                }
                finally
                {
                    _waitingOnFreeThreadCounter.Decrement();
                }
            }
        }