/// <summary> /// Executes specified task and starts cooresponding callback necessary for cooperation. /// </summary> /// <param name="taskWithInformation">Task to be executed</param> private void RunTask(PrioritizedLimitedTask taskWithInformation) { lock (schedulingLocker) { executingTasks.TryAdd(taskWithInformation.PrioritizedLimitedTaskIdentifier, taskWithInformation); // If pending task was paused, than callback exist and does not need to be started again if (!taskWithInformation.CooperationMechanism.IsPaused && !taskWithInformation.CooperationMechanism.IsResumed) { StartCallback(taskWithInformation, () => executingTasks.Remove(taskWithInformation.PrioritizedLimitedTaskIdentifier, out _), () => PauseCallback(taskWithInformation)); } // Do not block a main thread. new Task(() => { if (taskWithInformation.CooperationMechanism.IsPaused) { taskWithInformation.CooperationMechanism.Resume(); } else { TryExecuteTask(taskWithInformation); } }).Start(); } }
/// <summary> /// Using preemptive algorithm, runs tasks (number of tasks dependins on parallelism level). /// Deadlock avoidance is enabled. /// </summary> public override void RunScheduling() { lock (schedulingLocker) while (!pendingTasks.IsEmpty && executingTasks.Count < maxLevelOfParallelism) { // Get task that is next for execution (task with highest priority) PrioritizedLimitedTask taskWithInformation = GetNextTaskWithDeadlockAvoidance(); executingTasks.TryAdd(taskWithInformation.PrioritizedLimitedTaskIdentifier, taskWithInformation); // If pending task was paused, than callback exist and does not need to be created again if (!taskWithInformation.CooperationMechanism.IsPaused && !taskWithInformation.CooperationMechanism.IsResumed) { StartCallback(taskWithInformation, () => executingTasks.Remove(taskWithInformation.PrioritizedLimitedTaskIdentifier, out _), () => PauseCallback(taskWithInformation)); } // Do not block a main thread. new Task(() => { if (taskWithInformation.CooperationMechanism.IsPaused) { taskWithInformation.CooperationMechanism.Resume(); } else { TryExecuteTask(taskWithInformation); } }).Start(); } }
/// <summary> /// Delays execution of a callback for specific amount of time. /// </summary> /// <param name="taskWithInformation">Task that contains information about that time.</param> private void PauseCallback(PrioritizedLimitedTask taskWithInformation) { do { if (taskWithInformation.CooperationMechanism.IsPaused || taskWithInformation.CooperationMechanism.IsResumed) { Task.Delay(taskWithInformation.CooperationMechanism.PausedFor).Wait(); } }while (taskWithInformation.CooperationMechanism.IsPaused); }
/// <summary> /// If a task uses no shared resources, deadlock will be avoided. Otherwise, /// call is delegated to a manager that uses Banker's algorithm and decides whether deadlock can be avoided <see cref="IBankerAlgorithm"/>. /// </summary> private bool CanAvoidDeadlock(PrioritizedLimitedTask task) { lock (schedulingLocker) { RequestApproval approved = RequestApproval.Approved; if (task.UsesSharedResources()) { approved = sharedResourceManager.AllocateResources(task.PrioritizedLimitedTaskIdentifier , task.SharedResources); } return(approved == RequestApproval.Approved); } }
/// <summary> /// Starts (creates and schedules) task that is used to cooperatively cancels (and optionally pauses) user's task. /// Is user's task used resources, resources are released. /// This task is scheduled using default .NET scheduler. /// </summary> /// <param name="task">Corresponding user's task</param> /// <param name="controlNumberOfExecutionTasksAction">Responsible for decrementing number of currently running tasks</param> /// <param name="enablePauseAction">Responsible for pausing this callback for specific amount of time (optional)</param> protected void StartCallback(PrioritizedLimitedTask task, Action controlNumberOfExecutionTasksAction, Action enablePauseAction = null) => Task.Factory.StartNew(() => { Task.Delay(task.DurationInMiliseconds).Wait(); enablePauseAction?.Invoke(); task.CooperationMechanism.Cancel(); // Free shared resources if (task.UsesSharedResources()) { sharedResourceManager.FreeResources(task.PrioritizedLimitedTaskIdentifier, task.SharedResources); } controlNumberOfExecutionTasksAction.Invoke(); RunScheduling(); }, CancellationToken.None, TaskCreationOptions.None, Default);
/// <summary> /// Using preemptive scheduling algorithm, runs task. /// </summary> public override void RunScheduling() { lock (schedulingLocker) { while (!pendingTasks.IsEmpty && currentlyRunningTasks < maxLevelOfParallelism) { PrioritizedLimitedTask taskWithInformation = GetNextTaskWithDeadlockAvoidance(); StartCallback(taskWithInformation, () => Interlocked.Decrement(ref currentlyRunningTasks)); Interlocked.Increment(ref currentlyRunningTasks); // Do not block a main thread new Task(() => TryExecuteTask(taskWithInformation)).Start(); } } }
/// <summary> /// Finds and returns a task that has all necessary resources and allocates those resources. /// It assumes that list is not empty, so a caller of this method needs to check that. /// </summary> protected PrioritizedLimitedTask GetNextTaskWithDeadlockAvoidance() { pendingTasks.TryDequeue(out PrioritizedLimitedTask taskWithInformation); if (taskWithInformation.UsesSharedResources()) { RequestApproval approved = sharedResourceManager.AllocateResources(taskWithInformation.PrioritizedLimitedTaskIdentifier , taskWithInformation.SharedResources); if (approved == RequestApproval.Wait) { PrioritizedLimitedTask nextTask = GetNextTaskWithDeadlockAvoidance(); pendingTasks.Enqueue(taskWithInformation); taskWithInformation = nextTask; } } return(taskWithInformation); }
/// <summary> /// This method simulates a context switch (pauses one task, and runs another) if there is a guarantee that deadlock won't happen. /// If a task does not get necessary resources, normal scheduling is called (no context switching and no interruption). /// </summary> /// <param name="taskForExecution">Task to be executed</param> /// <param name="taskToPause">Task to pause</param> private void RequestContextSwitch(PrioritizedLimitedTask taskForExecution, PrioritizedLimitedTask taskToPause) { lock (schedulingLocker) // TODO: Move if (executingTasks.Count >= maxLevelOfParallelism && CanAvoidDeadlock(taskForExecution)) { executingTasks.Remove(taskToPause.PrioritizedLimitedTaskIdentifier, out _); taskToPause.CooperationMechanism.Pause(taskToPause.DurationInMiliseconds); pendingTasks.Enqueue(taskToPause); RunTask(taskForExecution); } else { SortPendingTasks(); RunScheduling(); } }
/// <summary> /// Schedules task based on a priority. If the task has a greater prirority than one of the currently running tasks, /// and if one of the currently running tasks allows cooperative context-switching, than context-switching will happen. /// </summary> /// <param name="task">Task to be scheduled (must extend <see cref="PrioritizedLimitedTask"/>), /// otherwise exception will be thrown</param> /// <exception cref="InvalidTaskException"></exception> protected override void QueueTask(Task task) { if (!(task is PrioritizedLimitedTask)) { throw new InvalidTaskException(); } pendingTasks.Enqueue(task as PrioritizedLimitedTask); Priority priority = (task as PrioritizedLimitedTask).Priority; PriorityComparer priorityComparer = new PriorityComparer(); PrioritizedLimitedTask taskToPause = executingTasks.Values .Where(x => x.CooperationMechanism.CanBePaused) .Min(); if (taskToPause != null && priority.CompareTo(taskToPause.Priority) > 0) { RequestContextSwitch(task as PrioritizedLimitedTask, taskToPause); } else { SortPendingTasks(); RunScheduling(); } }