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); }