public static void TaskContinuation() { int taskCount = Environment.ProcessorCount; int maxDOP = Int32.MaxValue; int maxNumberExecutionsPerTask = 1; int data = 0; Task[] allTasks = new Task[taskCount + 1]; CancellationTokenSource[] cts = new CancellationTokenSource[taskCount + 1]; for (int i = 0; i <= taskCount; i++) { cts[i] = new CancellationTokenSource(); } CancellationTokenSource cts2 = new CancellationTokenSource(); ConcurrentExclusiveSchedulerPair scheduler = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, maxDOP, maxNumberExecutionsPerTask); for (int i = 0; i <= taskCount; i++) { int j = i; allTasks[i] = new Task(() => { new TaskFactory(TaskScheduler.Current).StartNew(() => { }). ContinueWith((task, o) => { int d = (int)o; Interlocked.Add(ref data, d); }, j). ContinueWith((task, o) => { int d = (int)o; Interlocked.Add(ref data, d); cts[d].Cancel(); if (d <= taskCount) { throw new OperationCanceledException(cts[d].Token); } return "Done"; }, j, cts[j].Token). ContinueWith((task, o) => { int d = (int)o; Interlocked.Add(ref data, d); }, j, CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.Default).Wait(Int32.MaxValue - 1, cts2.Token); }); allTasks[i].Start(scheduler.ConcurrentScheduler); } Task.WaitAll(allTasks, int.MaxValue - 1, CancellationToken.None); Debug.WriteLine("Tasks ended: result {0}", data); Task completion = scheduler.Completion; scheduler.Complete(); completion.Wait(); int expectedResult = 3 * taskCount * (taskCount + 1) / 2; Assert.Equal(expectedResult, data); Assert.NotEqual(TaskScheduler.Default.Id, scheduler.ConcurrentScheduler.Id); Assert.NotEqual(TaskScheduler.Default.Id, scheduler.ExclusiveScheduler.Id); }
public static void TestCreationOptions(String ctorType) { ConcurrentExclusiveSchedulerPair schedPair = null; //Need to define the default values since these values are passed to the verification methods TaskScheduler scheduler = TaskScheduler.Default; int maxConcurrentLevel = Environment.ProcessorCount; //Based on input args, use one of the ctor overloads switch (ctorType.ToLower()) { case "default": schedPair = new ConcurrentExclusiveSchedulerPair(); break; case "scheduler": schedPair = new ConcurrentExclusiveSchedulerPair(scheduler); break; case "maxconcurrent": maxConcurrentLevel = 2; schedPair = new ConcurrentExclusiveSchedulerPair(scheduler, maxConcurrentLevel); break; case "all": maxConcurrentLevel = Int32.MaxValue; schedPair = new ConcurrentExclusiveSchedulerPair(scheduler, -1/*MaxConcurrentLevel*/, -1/*MaxItemsPerTask*/); //-1 gets converted to Int32.MaxValue break; default: throw new NotImplementedException(String.Format("The option specified {0} to create the ConcurrentExclusiveSchedulerPair is invalid", ctorType)); } //Create the factories that use the exclusive scheduler and the concurrent scheduler. We test to ensure //that the ConcurrentExclusiveSchedulerPair created are valid by scheduling work on them. TaskFactory writers = new TaskFactory(schedPair.ExclusiveScheduler); TaskFactory readers = new TaskFactory(schedPair.ConcurrentScheduler); List<Task> taskList = new List<Task>(); //Store all tasks created, to enable wait until all of them are finished // Schedule some dummy work that should be run with as much parallelism as possible for (int i = 0; i < 50; i++) { //In the current design, when there are no more tasks to execute, the Task used by concurrentexclusive scheduler dies //by sleeping we simulate some non trival work that takes time and causes the concurrentexclusive scheduler Task //to stay around for addition work. taskList.Add(readers.StartNew(() => { new ManualResetEvent(false).WaitOne(10); })); } // Schedule work where each item must be run when no other items are running for (int i = 0; i < 10; i++) taskList.Add(writers.StartNew(() => { new ManualResetEvent(false).WaitOne(5); })); //Wait on the tasks to finish to ensure that the ConcurrentExclusiveSchedulerPair created can schedule and execute tasks without issues foreach (var item in taskList) { item.Wait(); } //verify that maxconcurrency was respected. if (ctorType == "maxconcurrent") { Assert.Equal(maxConcurrentLevel, schedPair.ConcurrentScheduler.MaximumConcurrencyLevel); } Assert.Equal(1, schedPair.ExclusiveScheduler.MaximumConcurrencyLevel); //verify that the schedulers have not completed Assert.False(schedPair.Completion.IsCompleted, "The schedulers should not have completed as a completion request was not issued."); //complete the scheduler and make sure it shuts down successfully schedPair.Complete(); schedPair.Completion.Wait(); //make sure no additional work may be scheduled foreach (var schedPairScheduler in new TaskScheduler[] { schedPair.ConcurrentScheduler, schedPair.ExclusiveScheduler }) { Exception caughtException = null; try { Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, schedPairScheduler); } catch (Exception exc) { caughtException = exc; } Assert.True( caughtException is TaskSchedulerException && caughtException.InnerException is InvalidOperationException, "Queueing after completion should fail"); } }
public static void TestCompletionTask() { // Completion tasks is valid after initialization { var cesp = new ConcurrentExclusiveSchedulerPair(); Assert.True(cesp.Completion != null, "CompletionTask should never be null (after initialization)"); Assert.True(!cesp.Completion.IsCompleted, "CompletionTask should not have completed"); } // Completion task is valid after complete is called { var cesp = new ConcurrentExclusiveSchedulerPair(); cesp.Complete(); Assert.True(cesp.Completion != null, "CompletionTask should never be null (after complete)"); cesp.Completion.Wait(); } // Complete method may be called multiple times, and CompletionTask still completes { var cesp = new ConcurrentExclusiveSchedulerPair(); for (int i = 0; i < 20; i++) cesp.Complete(); // ensure multiple calls to Complete succeed Assert.True(cesp.Completion != null, "CompletionTask should never be null (after multiple completes)"); cesp.Completion.Wait(); } // Can create a bunch of schedulers, do work on them all, complete them all, and they all complete { var cesps = new ConcurrentExclusiveSchedulerPair[100]; for (int i = 0; i < cesps.Length; i++) { cesps[i] = new ConcurrentExclusiveSchedulerPair(); } for (int i = 0; i < cesps.Length; i++) { Action work = () => new ManualResetEvent(false).WaitOne(2); ; Task.Factory.StartNew(work, CancellationToken.None, TaskCreationOptions.None, cesps[i].ConcurrentScheduler); Task.Factory.StartNew(work, CancellationToken.None, TaskCreationOptions.None, cesps[i].ExclusiveScheduler); } for (int i = 0; i < cesps.Length; i++) { cesps[i].Complete(); cesps[i].Completion.Wait(); } } // Validate that CESP does not implement IDisposable Assert.Equal(null, new ConcurrentExclusiveSchedulerPair() as IDisposable); }
public static void TestMaxItemsPerTask(int maxConcurrency, int maxItemsPerTask, bool completeBeforeTaskWait) { //Create a custom TaskScheduler with specified max concurrency (TrackingTaskScheduler is defined in Common\tools\CommonUtils\TPLTestSchedulers.cs) TrackingTaskScheduler scheduler = new TrackingTaskScheduler(maxConcurrency); //We need to use the custom scheduler to achieve the results. As a by-product, we test to ensure custom schedulers are supported ConcurrentExclusiveSchedulerPair schedPair = new ConcurrentExclusiveSchedulerPair(scheduler, maxConcurrency, maxItemsPerTask); TaskFactory readers = new TaskFactory(schedPair.ConcurrentScheduler); //get reader and writer schedulers TaskFactory writers = new TaskFactory(schedPair.ExclusiveScheduler); //These are threadlocals to ensure that no concurrency side effects occur ThreadLocal<int> itemsExecutedCount = new ThreadLocal<int>(); //Track the items executed by CEScheduler Task ThreadLocal<int> schedulerIDInsideTask = new ThreadLocal<int>(); //Used to store the Scheduler ID observed by a Task Executed by CEScheduler Task //Work done by both reader and writer tasks Action work = () => { //Get the id of the parent Task (which is the task created by the scheduler). Each task run by the scheduler task should //see the same SchedulerID value since they are run on the same thread int id = ((TrackingTaskScheduler)scheduler).SchedulerID.Value; if (id == schedulerIDInsideTask.Value) { //since ids match, this is one more Task being executed by the CEScheduler Task itemsExecutedCount.Value = ++itemsExecutedCount.Value; //This does not need to be thread safe since we are looking to ensure that only n number of tasks were executed and not the order //in which they were executed. Also asserting inside the thread is fine since we just want the test to be marked as failure Assert.True(itemsExecutedCount.Value <= maxItemsPerTask, string.Format("itemsExecutedCount={0} cant be greater than maxValue={1}. Parent TaskID={2}", itemsExecutedCount, maxItemsPerTask, id)); } else { //Since ids dont match, this is the first Task being executed in the CEScheduler Task schedulerIDInsideTask.Value = id; //cache the scheduler ID seen by the thread, so other tasks running in same thread can see this itemsExecutedCount.Value = 1; } //Give enough time for a Task to stay around, so that other tasks will be executed by the same CEScheduler Task //or else the CESchedulerTask will die and each Task might get executed by a different CEScheduler Task. This does not affect the //verifications, but its increases the chance of finding a bug if the maxItemPerTask is not respected new ManualResetEvent(false).WaitOne(20); }; List<Task> taskList = new List<Task>(); int maxConcurrentTasks = maxConcurrency * maxItemsPerTask * 5; int maxExclusiveTasks = maxConcurrency * maxItemsPerTask * 2; // Schedule Tasks in both concurrent and exclusive mode for (int i = 0; i < maxConcurrentTasks; i++) taskList.Add(readers.StartNew(work)); for (int i = 0; i < maxExclusiveTasks; i++) taskList.Add(writers.StartNew(work)); if (completeBeforeTaskWait) { schedPair.Complete(); schedPair.Completion.Wait(); Assert.True(taskList.TrueForAll(t => t.IsCompleted), "All tasks should have completed for scheduler to complete"); } //finally wait for all of the tasks, to ensure they all executed properly Task.WaitAll(taskList.ToArray()); if (!completeBeforeTaskWait) { schedPair.Complete(); schedPair.Completion.Wait(); Assert.True(taskList.TrueForAll(t => t.IsCompleted), "All tasks should have completed for scheduler to complete"); } }
public void RunConcurrentExclusiveSchedulerPairRWTests() { // Validate reader tasks get scheduled concurrently { bool localPassed = true; foreach (var cesp in new[] { new ConcurrentExclusiveSchedulerPair(), new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, -1) }) { try { int numToSchedule = Environment.ProcessorCount; // doesn't validate much on a single core, but that's ok Barrier b = new Barrier(numToSchedule); var tasks = new Task[numToSchedule]; for (int i = 0; i < numToSchedule; i++) { tasks[i] = Task.Factory.StartNew(() => { localPassed &= b.SignalAndWait(1000); }, CancellationToken.None, TaskCreationOptions.None, cesp.ConcurrentScheduler); } Task.WaitAll(tasks); } finally { cesp.Complete(); } } Assert.True(localPassed, string.Format("{0}: Test concurrent readers", localPassed ? "Success" : "Failure")); } // Validate reader tasks don't go above max concurrency level { bool localPassed = true; int mcl = 2; var cesp = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, mcl); try { int numToSchedule = 2; int concurrentTasks = 0; var tasks = new Task[numToSchedule]; for (int i = 0; i < numToSchedule; i++) { tasks[i] = Task.Factory.StartNew(() => { Interlocked.Increment(ref concurrentTasks); Task.Delay(1).Wait(); if (concurrentTasks > mcl) localPassed = false; Task.Delay(1).Wait(); Interlocked.Decrement(ref concurrentTasks); }, CancellationToken.None, TaskCreationOptions.None, cesp.ConcurrentScheduler); } Task.WaitAll(tasks); } finally { cesp.Complete(); } Assert.True(localPassed, string.Format("{0}: Test concurrent readers stay below maximum", localPassed ? "Success" : "Failure")); } // Validate writers tasks don't run concurrently with each other { bool localPassed = true; var cesp = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default); try { int numToSchedule = 2; int concurrentTasks = 0; var tasks = new Task[numToSchedule]; for (int i = 0; i < numToSchedule; i++) { tasks[i] = Task.Factory.StartNew(() => { Interlocked.Increment(ref concurrentTasks); Task.Delay(100).Wait(); if (concurrentTasks > 1) localPassed &= false; Task.Delay(100).Wait(); Interlocked.Decrement(ref concurrentTasks); }, CancellationToken.None, TaskCreationOptions.None, cesp.ExclusiveScheduler); } Task.WaitAll(tasks); } finally { cesp.Complete(); } Assert.True(localPassed, string.Format("{0}: Test writers don't run concurrently with each other", localPassed ? "Success" : "Failure")); } // Validate writers tasks don't run concurrently with readers or writers { bool localPassed = true; var cesp = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default); int numWritersToSchedule = 2; int numReadersToSchedule = 3; int runningTasks = 0; var readerTasks = new Task[numReadersToSchedule]; for (int i = 0; i < numReadersToSchedule; i++) { readerTasks[i] = Task.Factory.StartNew(() => { Interlocked.Increment(ref runningTasks); Task.Delay(1).Wait(); Interlocked.Decrement(ref runningTasks); }, CancellationToken.None, TaskCreationOptions.None, cesp.ConcurrentScheduler); } var writerTasks = new Task[numWritersToSchedule]; for (int i = 0; i < numWritersToSchedule; i++) { writerTasks[i] = Task.Factory.StartNew(() => { Interlocked.Increment(ref runningTasks); Task.Delay(100).Wait(); if (runningTasks > 1) localPassed &= false; Task.Delay(100).Wait(); Interlocked.Decrement(ref runningTasks); }, CancellationToken.None, TaskCreationOptions.None, cesp.ExclusiveScheduler); } Task.WaitAll(writerTasks); Task.WaitAll(readerTasks); // not completing the cesp here, just so that some of our tests don't // and so that we don't hide any issues by always completing Assert.True(localPassed, string.Format("{0}: Test writers don't run concurrently with anything", localPassed ? "Success" : "Failure")); } }
public void RunConcurrentExclusiveSchedulerPairTests() { // Validate invalid arguments { Assert.Throws<ArgumentNullException>(() => new ConcurrentExclusiveSchedulerPair(null)); Assert.Throws<ArgumentOutOfRangeException>(() => new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, -2)); Assert.Throws<ArgumentOutOfRangeException>(() => new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 0)); Assert.Throws<ArgumentOutOfRangeException>(() => new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 1, -2)); Assert.Throws<ArgumentOutOfRangeException>(() => new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 1, 0)); } // Validate completion prevents more tasks { bool localPassed = true; ConcurrentExclusiveSchedulerPair cesp = new ConcurrentExclusiveSchedulerPair(); cesp.Complete(); Assert.Throws<TaskSchedulerException>(() => Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, cesp.ConcurrentScheduler).Wait()); Assert.Throws<TaskSchedulerException>(() => Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait()); Assert.True(localPassed, string.Format("{0}: Completion prevents more tasks", localPassed ? "Success" : "Failure")); } // Validate completion allows existing scheduled tasks to complete { bool localPassed = true; ConcurrentExclusiveSchedulerPair cesp = new ConcurrentExclusiveSchedulerPair(); int tasksToSchedule = 2; int count = 0; var tasks = new Task[tasksToSchedule]; using (var mres = new ManualResetEventSlim()) { for (int i = 0; i < tasksToSchedule; i++) { tasks[i] = Task.Factory.StartNew(() => { mres.Wait(); Interlocked.Increment(ref count); }, CancellationToken.None, TaskCreationOptions.None, cesp.ExclusiveScheduler); } cesp.Complete(); Assert.True(count == 0, "No tasks should have completed yet"); mres.Set(); Task.WaitAll(tasks); Assert.True(count == tasksToSchedule, "All of the tasks should have executed"); cesp.Completion.Wait(); } Assert.True(localPassed, string.Format("{0}: Completion allows existing tasks to complete", localPassed ? "Success" : "Failure")); } // Validate MCL handling { bool localPassed = true; { var cesp = new ConcurrentExclusiveSchedulerPair(new ControllableMclTaskScheduler(4), 8); localPassed &= cesp.ConcurrentScheduler.MaximumConcurrencyLevel == 4; localPassed &= cesp.ExclusiveScheduler.MaximumConcurrencyLevel == 1; } { var cesp = new ConcurrentExclusiveSchedulerPair(new ControllableMclTaskScheduler(8), 4); localPassed &= cesp.ConcurrentScheduler.MaximumConcurrencyLevel == 4; localPassed &= cesp.ExclusiveScheduler.MaximumConcurrencyLevel == 1; } { var cesp = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, -1); localPassed &= cesp.ConcurrentScheduler.MaximumConcurrencyLevel == Int32.MaxValue; localPassed &= cesp.ExclusiveScheduler.MaximumConcurrencyLevel == 1; } { var cesp = new ConcurrentExclusiveSchedulerPair(new ControllableMclTaskScheduler(-2), -1); localPassed &= cesp.ConcurrentScheduler.MaximumConcurrencyLevel == Int32.MaxValue; localPassed &= cesp.ExclusiveScheduler.MaximumConcurrencyLevel == 1; } { var cesp = new ConcurrentExclusiveSchedulerPair(new ControllableMclTaskScheduler(-2), 1); localPassed &= cesp.ConcurrentScheduler.MaximumConcurrencyLevel == 1; localPassed &= cesp.ExclusiveScheduler.MaximumConcurrencyLevel == 1; } Assert.True(localPassed, string.Format("{0}: Max Concurrency Level corner cases handling correctly", localPassed ? "Success" : "Failure")); } // Validate queueing when layering on a faulty scheduler { bool localPassed = true; var ts = new ControllableTaskScheduler(); // NOTE: We're using new pair instances on each iteration to avoid code paths // where a replica task gets queued up in the scheduler. If that happens while the underlying // scheduler is FailQueueing==true, the task used internally by CESP will fault // and will go unobserved. This is by design and we don't want it bringing down the tests. var cesp1 = new ConcurrentExclusiveSchedulerPair(ts); var cesp2 = new ConcurrentExclusiveSchedulerPair(ts); foreach (var cesp in new[] { cesp1, cesp2 }) { var scheduler = cesp == cesp1 ? cesp1.ConcurrentScheduler : cesp2.ExclusiveScheduler; // Queue a task that will cause the CESP to fail queueing to its underlying scheduler ts.FailQueueing = true; Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, scheduler); try { Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, scheduler); localPassed = false; } catch (Exception exc) { Assert.True( exc is TaskSchedulerException && ((TaskSchedulerException)exc).InnerException is InvalidOperationException, "Expected a TaskSchedulerException containing an InvalidOperationException"); } Assert.True(SpinWait.SpinUntil(() => cesp.Completion.IsCompleted, 1), "Excepted CESP to complete in allotted time"); Assert.True(cesp.Completion.Exception != null, "Excepted CESP task to have exceptions"); } Assert.True(localPassed, string.Format("{0}: Test queueing layering on faulty scheduler", localPassed ? "Success" : "Failure")); } // Validate inlining when layering on a faulty scheduler { bool localPassed = true; var ts = new ControllableTaskScheduler(); // NOTE: We're using new pair instances on each iteration to avoid code paths // where a replica task gets queued up in the scheduler. If that happens while the underlying // scheduler is FailQueueing==true, the task used internally by CESP will fault // and will go unobserved. This is by design and we don't want it bringing down the tests. var cesp1 = new ConcurrentExclusiveSchedulerPair(ts); var cesp2 = new ConcurrentExclusiveSchedulerPair(ts); foreach (var cesp in new[] { cesp1, cesp2 }) { var scheduler = cesp == cesp1 ? cesp1.ConcurrentScheduler : cesp2.ExclusiveScheduler; // Inline a task that will cause the CESP to fail queueing to its underlying scheduler ts.FailQueueing = false; Task.Factory.StartNew(() => { ts.FailQueueing = true; Task t = new Task(() => { }); try { t.RunSynchronously(scheduler); localPassed = false; } catch (Exception exc) { Assert.True( exc is TaskSchedulerException && ((TaskSchedulerException)exc).InnerException is TaskSchedulerException, "Excepted a TaskSchedulerException to contain another TaskSchedulerException"); } Assert.True(t.IsCompleted, "Expected the queued task to be completed"); Assert.True(t.Exception != null, "Expected the queued task to be faulted"); }, CancellationToken.None, TaskCreationOptions.None, scheduler).Wait(); ts.FailQueueing = false; Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, scheduler).Wait(); cesp.Complete(); Assert.True(SpinWait.SpinUntil(() => cesp.Completion.IsCompleted, 1), "Expected the CESP to complete in the allotted time"); Assert.True(cesp.Completion.Exception == null, "Expected the task to not be faulted and have no exceptions"); } Assert.True(localPassed, string.Format("{0}: Test inlining layering on faulty scheduler", localPassed ? "Success" : "Failure")); } // Validate tasks on the same scheduler waiting on each other { bool localPassed = true; foreach (var underlyingScheduler in new[] { TaskScheduler.Default, new ControllableTaskScheduler() { FailQueueing = false } }) { var cesp = new ConcurrentExclusiveSchedulerPair(underlyingScheduler); TaskRecursion(2, new ParallelOptions { TaskScheduler = cesp.ExclusiveScheduler }); cesp.Complete(); cesp = new ConcurrentExclusiveSchedulerPair(underlyingScheduler); TaskRecursion(2, new ParallelOptions { TaskScheduler = cesp.ConcurrentScheduler }); cesp.Complete(); } Assert.True(localPassed, string.Format("{0}: Recursively waiting on same scheduler", localPassed ? "Success" : "Failure")); } // Exercise additional inlining code paths { bool localPassed = true; foreach (var underlyingScheduler in new[] { TaskScheduler.Default, new ControllableTaskScheduler() { FailQueueing = false } }) { var cesp = new ConcurrentExclusiveSchedulerPair(underlyingScheduler); Task.Factory.StartNew(() => { }).ContinueWith(_ => { }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, cesp.ExclusiveScheduler).Wait(); Task.Factory.StartNew(() => { }).ContinueWith(_ => { }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, cesp.ConcurrentScheduler).Wait(); var t = new Task(() => { }); t.RunSynchronously(cesp.ConcurrentScheduler); t.Wait(); t = new Task(() => { }); t.RunSynchronously(cesp.ExclusiveScheduler); t.Wait(); Task.Factory.StartNew(() => { new Task(() => { }, TaskCreationOptions.AttachedToParent).RunSynchronously(cesp.ConcurrentScheduler); }, CancellationToken.None, TaskCreationOptions.None, cesp.ConcurrentScheduler).Wait(); Task.Factory.StartNew(() => { new Task(() => { }, TaskCreationOptions.AttachedToParent).RunSynchronously(cesp.ExclusiveScheduler); }, CancellationToken.None, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait(); } Assert.True(localPassed, string.Format("{0}: Additional inlining code paths", localPassed ? "Success" : "Failure")); } }