/// <summary>
        /// Schedules new task. The coalesce decision callback will be called for pending and running tasks
        /// before returning from this method.
        /// </summary>
        /// <param name="action">The task to schedule.</param>
        /// <param name="arg">The action to schedule. Will be passed as an argument to the coalesce decision callback.</param>
        /// <returns>The result returned by the task.</returns>
        public Task <TResult> Enqueue(Func <CancellationToken, Task <TResult> > action, TArg arg)
        {
            CoalescingTaskQueueData <TArg, TResult> work;
            CoalescingTaskQueueData <TArg, TResult> newWork;
            CoalescingTaskQueueData <TArg, TResult> previousWork;
            bool cancelRunningTask;
            QueuedTask <TArg, TResult> newTask = null;

            do
            {
                work = _work;
                cancelRunningTask = false;
                newTask?.Dispose();

                if (work.PendingTask != null)
                {
                    // Makes decision on pending task
                    var decision = _decisionCallback(arg, work.PendingTask.Arg, false);
                    if (decision == CoalesceDecision.Join)
                    {
                        return(_work.PendingTask.Task);
                    }
                }

                if (work.RunningTask != null && !work.RunningTask.CancellationRequested)
                {
                    // Makes decision on running task
                    var decision = _decisionCallback(arg, work.RunningTask.Arg, true);
                    if (decision == CoalesceDecision.Join)
                    {
                        return(_work.RunningTask.Task);
                    }

                    // Doesn't cancel running task until successfully scheduling new task, will do that later.
                    cancelRunningTask = decision == CoalesceDecision.Cancel;
                }

                newTask = new QueuedTask <TArg, TResult>(action, arg);

                if (work.RunningTask == null)
                {
                    // No task is running. Schedule new running task, will start it later.
                    newWork = new CoalescingTaskQueueData <TArg, TResult>(
                        null,
                        newTask);
                }
                else
                {
                    // A task is running. Schedule new pending task.
                    newWork = new CoalescingTaskQueueData <TArg, TResult>(
                        newTask,
                        work.RunningTask);
                }

                previousWork = Interlocked.CompareExchange(ref _work, newWork, work);
            } while (previousWork != work);

            // Scheduling succeeded

            // Cancel previous pending task if any
            var pendingTask = work.PendingTask;

            if (pendingTask != null)
            {
                pendingTask.Cancel(false);
                pendingTask.Dispose();
            }

            // Cancel running task if requested by decision callback
            if (cancelRunningTask)
            {
                work.RunningTask.Cancel(true);
            }

            // Start new task if scheduled as running
            if (newWork.RunningTask == newTask)
            {
                Run(newTask);
            }

            return(newTask.Task);
        }