public async System.Threading.Tasks.Task AddTasksAsync(IEnumerable <CloudTask> tasksToAdd, TimeSpan?timeout = null)
        {
            //Ensure that this object has not already been used
            int original = Interlocked.CompareExchange(ref this._hasRun, HasRun, HasNotRun);

            if (original != HasNotRun)
            {
                throw new RunOnceException(string.Format(BatchErrorMessages.CanOnlyBeRunOnceFailure, this.GetType().Name));
            }

            //Determine what time to timeout at
            if (timeout != null)
            {
                this._timeToTimeoutAt = DateTime.UtcNow + timeout.Value;
            }
            else
            {
                //TODO: We set this to max value -- maybe in the future we can be a bit more friendly to customers and not run forever
                this._timeToTimeoutAt = DateTime.MaxValue;
            }

            //
            // Collect the tasks and add them to the pending queue
            //
            //TODO: There is a tension between allowing lazy enumeration of tasksToAdd and performing any validation on tasksToAdd early in the addition process
            //TODO: For now (since most customer implementations are not going to write their own lazy collection) we go ahead and perform
            //TODO: some input validation...

            //
            // Perform some basic input validation
            //
            //TODO: Check no duplicate task names?
            //TODO: Check the tasks are not children of some other object already

            //Enumerate the supplied collection asynchronously if required
            tasksToAdd = await UtilitiesInternal.EnumerateIfNeededAsync(tasksToAdd, this._parallelOptions.CancellationToken).ConfigureAwait(false);

            //TODO: For now perform a copy into a queue -- in the future consider honoring lazy loading and do this later
            foreach (CloudTask cloudTask in tasksToAdd)
            {
                if (cloudTask == null)
                {
                    throw new ArgumentNullException("tasksToAdd", BatchErrorMessages.CollectionMustNotContainNull);
                }

                if (cloudTask.BindingState == BindingState.Bound)
                {
                    throw UtilitiesInternal.OperationForbiddenOnBoundObjects;
                }

                this._remainingTasksToAdd.Enqueue(new TrackedCloudTask(this._jobId, this._jobOperations, cloudTask));
            }

            //
            // Fire the requests
            //
            while (!this.IsWorkflowDone())
            {
                //Wait for an open request "slot" (an existing request to complete) if:
                //1. We have no free request slots
                //2. We have no more tasks to add and there are ongoing pending operations which could result in task add retries (causing us to get more tasks to add)
                bool noFreeSlots = this._pendingAsyncOperations.Count >= this._parallelOptions.MaxDegreeOfParallelism;
                bool noTasksToAddButSomePendingLegsRemain = this._remainingTasksToAdd.Count == 0 && this._pendingAsyncOperations.Count > 0;
                if (noFreeSlots || noTasksToAddButSomePendingLegsRemain)
                {
                    await this.ProcessPendingOperationResults().ConfigureAwait(continueOnCapturedContext: false);
                }

                //If we get here, we are starting a single new leg.  Another iteration of this loop will get to any tasks which do not fit in this leg.

                //Add any tasks (up to max count in 1 request) which are remaining since we have an open parallel slot
                Dictionary <string, TrackedCloudTask> nameToTaskMapping = new Dictionary <string, TrackedCloudTask>();

                //Attempt to take some items from the set of tasks remaining to be added and prepare it to be added
                TrackedCloudTask taskToAdd;
                int tmpMaxTasks = this._maxTasks;
                while (nameToTaskMapping.Count < tmpMaxTasks && this._remainingTasksToAdd.TryDequeue(out taskToAdd))
                {
                    nameToTaskMapping.Add(taskToAdd.Task.Id, taskToAdd);
                }

                if (nameToTaskMapping.Count > 0)
                {
                    //Start the async operation to stage the files (if required) and issue the protocol add task request
                    Task asyncTask = this.StageFilesAndAddTasks(
                        nameToTaskMapping,
                        null);

                    //Add the request to the operation tracker
                    this._pendingAsyncOperations.Add(asyncTask);
                }
            }

            //If we reach here, we have succeeded - yay!
        }
        private async Task WhenAllImplAsync(
            IEnumerable <CloudTask> tasksToMonitor,
            Common.TaskState desiredState,
            CancellationToken cancellationToken,
            ODATAMonitorControl controlParams,
            IEnumerable <BatchClientBehavior> additionalBehaviors)
        {
            if (null == tasksToMonitor)
            {
                throw new ArgumentNullException("tasksToMonitor");
            }

            // we only need the id and state for this monitor.  the filter clause will be updated by the monitor
            ODATADetailLevel odataSuperOptimalPredicates = new ODATADetailLevel()
            {
                SelectClause = "id,state"
            };

            // for validation and list calls we need the parent name values
            string jobId = null;

            // set up behaviors
            BehaviorManager bhMgr = new BehaviorManager(this.CustomBehaviors, additionalBehaviors);

            // set up control params if needed
            if (null == controlParams)
            {
                controlParams = new ODATAMonitorControl(); // use defaults
            }

            tasksToMonitor = await UtilitiesInternal.EnumerateIfNeededAsync(tasksToMonitor, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);

            // validation: job schedule id and jobId
            foreach (CloudTask curTask in tasksToMonitor)
            {
                // can only monitor bound objects
                if (curTask.BindingState != BindingState.Bound)
                {
                    Exception ex = UtilitiesInternal.OperationForbiddenOnUnboundObjects;

                    throw ex;
                }

                // set or validate job Id
                if (null == jobId)
                {
                    jobId = curTask.ParentJobId;
                }
                else
                {
                    // all instances must have same parent
                    if (!jobId.Equals(curTask.ParentJobId, StringComparison.OrdinalIgnoreCase))
                    {
                        Exception ex = UtilitiesInternal.MonitorRequiresConsistentHierarchyChain;

                        throw ex;
                    }
                }
            }

            // start call
            Task asyncTask = ODATAMonitor.WhenAllAsync(
                tasksToMonitor,
                x =>
            {
                // return true if is desired state
                bool hasReachedDesiredState = x.State == desiredState;
                return(hasReachedDesiredState);
            },
                x => { return(x.Id); },                                                                                          // return the Id of the task
                () => _parentUtilities.ParentBatchClient.JobOperations.ListTasksImpl(jobId, bhMgr, odataSuperOptimalPredicates), // call this lambda to (re)fetch the list
                cancellationToken,
                odataSuperOptimalPredicates,
                controlParams);

            await asyncTask.ConfigureAwait(continueOnCapturedContext : false);
        }