diff --git a/Directory.Build.targets b/Directory.Build.targets index d08b29eb3e..85fdcc8f0d 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -112,7 +112,6 @@ $(DefineConstants);FEATURE_ASSEMBLY_GETCALLINGASSEMBLY $(DefineConstants);FEATURE_FILESTREAM_LOCK $(DefineConstants);FEATURE_TEXTWRITER_CLOSE - $(DefineConstants);FEATURE_THREADPOOL_UNSAFEQUEUEWORKITEM $(DefineConstants);FEATURE_TYPE_GETMETHOD__BINDINGFLAGS_PARAMS diff --git a/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs index 990b2cd357..e892d8854b 100644 --- a/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs +++ b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs @@ -1,5 +1,15 @@ -using Lucene.Net.Util; +// From Apache Harmony tests: +// https://github.com/apache/harmony/blob/trunk/classlib/modules/concurrent/src/test/java/JSR166TestCase.java +using Lucene.Net.Util; using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using ThreadInterruptedException = System.Threading.ThreadInterruptedException; + +#nullable enable namespace Lucene.Net.Support.Threading { @@ -20,82 +30,90 @@ namespace Lucene.Net.Support.Threading * limitations under the License. */ - /** - * Base class for JSR166 Junit TCK tests. Defines some constants, - * utility methods and classes, as well as a simple framework for - * helping to make sure that assertions failing in generated threads - * cause the associated test that generated them to itself fail (which - * JUnit does not otherwise arrange). The rules for creating such - * tests are: - * - *
    - * - *
  1. All assertions in code running in generated threads must use - * the forms {@link #threadFail}, {@link #threadAssertTrue}, {@link - * #threadAssertEquals}, or {@link #threadAssertNull}, (not - * fail, assertTrue, etc.) It is OK (but not - * particularly recommended) for other code to use these forms too. - * Only the most typically used JUnit assertion methods are defined - * this way, but enough to live with.
  2. - * - *
  3. If you override {@link #setUp} or {@link #tearDown}, make sure - * to invoke super.setUp and super.tearDown within - * them. These methods are used to clear and check for thread - * assertion failures.
  4. - * - *
  5. All delays and timeouts must use one of the constants - * SHORT_DELAY_MS, SMALL_DELAY_MS, MEDIUM_DELAY_MS, - * LONG_DELAY_MS. The idea here is that a SHORT is always - * discriminable from zero time, and always allows enough time for the - * small amounts of computation (creating a thread, calling a few - * methods, etc) needed to reach a timeout point. Similarly, a SMALL - * is always discriminable as larger than SHORT and smaller than - * MEDIUM. And so on. These constants are set to conservative values, - * but even so, if there is ever any doubt, they can all be increased - * in one spot to rerun tests on slower platforms.
  6. - * - *
  7. All threads generated must be joined inside each test case - * method (or fail to do so) before returning from the - * method. The joinPool method can be used to do this when - * using Executors.
  8. - * - *
- * - *

Other notes - *

- */ + /// + /// LUCENENET NOTE: This class has been adapted from the Apache Harmony + /// tests. The original javadoc is included below, and adapted where necessary. + /// + /// + /// Base class for JSR166 Junit TCK tests. Defines some constants, + /// utility methods and classes, as well as a simple framework for + /// helping to make sure that assertions failing in generated threads + /// cause the associated test that generated them to itself fail (which + /// JUnit does not otherwise arrange). The rules for creating such + /// tests are: + /// + /// + /// + /// All assertions in code running in generated threads must use + /// the forms , , + /// , + /// or , (not + /// fail, assertTrue, etc.) It is OK (but not + /// particularly recommended) for other code to use these forms too. + /// Only the most typically used JUnit assertion methods are defined + /// this way, but enough to live with. + /// + /// If you override or , make sure + /// to invoke base.SetUp and base.TearDown within + /// them. These methods are used to clear and check for thread + /// assertion failures. + /// + /// All delays and timeouts must use one of the constants + /// , , , + /// . The idea here is that a SHORT is always + /// discriminable from zero time, and always allows enough time for the + /// small amounts of computation (creating a thread, calling a few + /// methods, etc) needed to reach a timeout point. Similarly, a SMALL + /// is always discriminable as larger than SHORT and smaller than + /// MEDIUM. And so on. These constants are set to conservative values, + /// but even so, if there is ever any doubt, they can all be increased + /// in one spot to rerun tests on slower platforms. + /// + /// All threads generated must be joined inside each test case + /// method (or fail to do so) before returning from the + /// method. The method can be used to do this when + /// using Executors. + /// + /// + /// + /// + /// Other notes + /// + /// + /// Usually, there is one testcase method per JSR166 method + /// covering "normal" operation, and then as many exception-testing + /// methods as there are exceptions the method can throw. Sometimes + /// there are multiple tests per JSR166 method when the different + /// "normal" behaviors differ significantly. And sometimes testcases + /// cover multiple methods when they cannot be tested in + /// isolation. + /// + /// The documentation style for testcases is to provide as javadoc + /// a simple sentence or two describing the property that the testcase + /// method purports to test. The javadocs do not say anything about how + /// the property is tested. To find out, read the code. + /// + /// These tests are "conformance tests", and do not attempt to + /// test throughput, latency, scalability or other performance factors + /// (see the separate "jtreg" tests for a set intended to check these + /// for the most central aspects of functionality.) So, most tests use + /// the smallest sensible numbers of threads, collection sizes, etc + /// needed to check basic conformance. + /// + /// The test classes currently do not declare inclusion in + /// any particular package to simplify things for people integrating + /// them in TCK test suites. + /// + /// + /// + /// + /// public class JSR166TestCase : LuceneTestCase { - ///** + // /** // * Runs all JSR166 unit tests using junit.textui.TestRunner // */ //public static void main(String[] args) @@ -255,7 +273,7 @@ public void threadAssertFalse(bool b) * If argument not null, set status to indicate current testcase * should fail */ - public void threadAssertNull(object x) + public void threadAssertNull(object? x) { if (x != null) { @@ -281,7 +299,7 @@ public void threadAssertEquals(long x, long y) * If arguments not equal, set status to indicate current testcase * should fail */ - public void threadAssertEquals(object x, object y) + public void threadAssertEquals(object? x, object? y) { if (x != y && (x == null || !x.equals(y))) { @@ -326,25 +344,25 @@ public void threadUnexpectedException(Exception ex) fail("Unexpected exception: " + ex); } - ///** - // * Wait out termination of a thread pool or fail doing so - // */ - //public void joinPool(ExecutorService exec) - //{ - // try - // { - // exec.shutdown(); - // assertTrue(exec.awaitTermination(LONG_DELAY_MS, TimeUnit.MILLISECONDS)); - // } - // catch (SecurityException ok) - // { - // // Allowed in case test doesn't have privs - // } - // catch (InterruptedException ie) - // { - // fail("Unexpected exception"); - // } - //} + /** + * Wait out termination of a thread pool or fail doing so + */ + public void joinPool(TaskScheduler exec) + { + try + { + exec.Shutdown(); + assertTrue(exec.AwaitTermination(TimeSpan.FromMilliseconds(LONG_DELAY_MS))); + } + // catch (SecurityException ok) // LUCENENET - not needed + // { + // // Allowed in case test doesn't have privs + // } + catch (ThreadInterruptedException /*ie*/) + { + fail("Unexpected exception"); + } + } /** @@ -363,7 +381,141 @@ public void unexpectedException() fail("Unexpected exception"); } + internal void ShortRunnable() + { + try + { + Thread.Sleep(SHORT_DELAY_MS); + } + catch (Exception e) + { + threadUnexpectedException(e); + } + } + + internal void MediumRunnable() + { + try + { + Thread.Sleep(MEDIUM_DELAY_MS); + } + catch (Exception e) + { + threadUnexpectedException(e); + } + } // LUCENENET TODO: Complete port } + + /// + /// LUCENENET specific - fake support for an API that feels like ThreadPoolExecutor. + /// + internal static class JSR166TestCaseExtensions + { + /// + /// LUCENENET specific - state to keep track of tasks. + /// removes tasks from the list when they complete, + /// so this class is needed to keep track of them. + /// + private class TaskState + { + private readonly TaskFactory _factory; + private readonly List _tasks = new(); + + public TaskState(TaskScheduler scheduler) + { + _factory = new TaskFactory(scheduler); + } + + public void NewTask(Action action) + { + var task = _factory.StartNew(action); + _tasks.Add(task); + } + + public int ActiveCount => _tasks.Count(t => t.Status == TaskStatus.Running); + + public int CompletedCount => _tasks.Count(t => t.IsCompleted); + + public int TaskCount => _tasks.Count; + + public bool AllCompleted => _tasks.All(t => t.IsCompleted); + + public bool JoinAll(TimeSpan timeout) => Task.WhenAll(_tasks).Wait(timeout); + } + + private static readonly ConditionalWeakTable _taskFactories = new(); + + public static void Execute(this TaskScheduler scheduler, Action action) + { + if (!_taskFactories.TryGetValue(scheduler, out TaskState? state)) + { + state = new TaskState(scheduler); + _taskFactories.Add(scheduler, state); + } + + state.NewTask(action); + } + + public static bool AwaitTermination(this TaskScheduler scheduler, TimeSpan timeout) + { + if (_taskFactories.TryGetValue(scheduler, out TaskState? state)) + { + return state.JoinAll(timeout); + } + + return true; + } + + public static int GetActiveCount(this TaskScheduler scheduler) + { + if (_taskFactories.TryGetValue(scheduler, out TaskState? state)) + { + // Approximate the number of running threads, which shouldn't exceed the concurrency level + return Math.Min(scheduler.MaximumConcurrencyLevel, state.ActiveCount); + } + + return 0; + } + + public static int GetCompletedTaskCount(this TaskScheduler scheduler) + { + if (_taskFactories.TryGetValue(scheduler, out TaskState? state)) + { + return state.CompletedCount; + } + + return 0; + } + + public static int GetTaskCount(this TaskScheduler scheduler) + { + if (_taskFactories.TryGetValue(scheduler, out TaskState? state)) + { + return state.TaskCount; + } + + return 0; + } + + public static void Shutdown(this TaskScheduler scheduler) + { + if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl) + { + lcl.Shutdown(); + } + } + + public static bool IsTerminated(this TaskScheduler scheduler) + { + if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl + && _taskFactories.TryGetValue(scheduler, out TaskState? state)) + { + return lcl.IsShutdown && state.AllCompleted; + } + + return false; // can't be shut down, so can't be terminated + } + } } diff --git a/src/Lucene.Net.Tests/Support/Threading/TestLimitedConcurrencyLevelTaskScheduler.cs b/src/Lucene.Net.Tests/Support/Threading/TestLimitedConcurrencyLevelTaskScheduler.cs new file mode 100644 index 0000000000..2952ce2590 --- /dev/null +++ b/src/Lucene.Net.Tests/Support/Threading/TestLimitedConcurrencyLevelTaskScheduler.cs @@ -0,0 +1,237 @@ +// Based on tests from Apache Harmony: +// https://github.com/apache/harmony/blob/02970cb7227a335edd2c8457ebdde0195a735733/classlib/modules/concurrent/src/test/java/ThreadPoolExecutorTest.java + +using NUnit.Framework; +using System; +using System.Threading; +using System.Threading.Tasks; +#nullable enable + +namespace Lucene.Net.Support.Threading +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /// + /// Tests for . + /// Adapted from Apache Harmony test suite ThreadPoolExecutorTest. + /// + [TestFixture] + public class TestLimitedConcurrencyLevelTaskScheduler : JSR166TestCase + { + /// + /// execute successfully executes a runnable + /// + /// + /// LUCENENET Note: Execute is provided in + /// to emulate the behavior of the Java method; it is not in the public + /// API for . This + /// just helps ensure the class is working as expected. + /// + [Test] + public void TestExecute() + { + TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1); + + try + { + p1.Execute(() => + { + try + { + Thread.Sleep(SHORT_DELAY_MS); + } + catch (ThreadInterruptedException /*e*/) + { + threadUnexpectedException(); + } + }); + Thread.Sleep(SMALL_DELAY_MS); + } + catch (ThreadInterruptedException /*e*/) + { + unexpectedException(); + } + + joinPool(p1); + } + + /// + /// getActiveCount increases but doesn't overestimate, when a + /// thread becomes active + /// + /// + /// LUCENENET Note: GetActiveCount is provided in + /// to emulate the behavior of the Java method; it is not in the public + /// API for . This + /// just helps ensure the class is working as expected. + /// + [Test] + public void TestGetActiveCount() + { + TaskScheduler p2 = new LimitedConcurrencyLevelTaskScheduler(2); + assertEquals(0, p2.GetActiveCount()); + p2.Execute(MediumRunnable); + + try + { + Thread.Sleep(SHORT_DELAY_MS); + } + catch (Exception /*e*/) + { + unexpectedException(); + } + + assertEquals(1, p2.GetActiveCount()); + joinPool(p2); + } + + // LUCENENET NOTE: testPrestartCoreThread and testPrestartAllCoreThreads omitted; they are not relevant + + /// + /// getCompletedTaskCount increases, but doesn't overestimate, + /// when tasks complete + /// + /// + /// LUCENENET Note: GetCompletedTaskCount is provided in + /// to emulate the behavior of the Java method; it is not in the public + /// API for . This + /// just helps ensure the class is working as expected. + /// + [Test] + public void TestGetCompletedTaskCount() + { + TaskScheduler p2 = new LimitedConcurrencyLevelTaskScheduler(2); + assertEquals(0, p2.GetCompletedTaskCount()); + p2.Execute(ShortRunnable); + + try + { + Thread.Sleep(SMALL_DELAY_MS); + } + catch (Exception /*e*/) + { + unexpectedException(); + } + + assertEquals(1, p2.GetCompletedTaskCount()); + // LUCENENET NOTE: not catching SecurityException because that's not relevant here + p2.Shutdown(); + joinPool(p2); + } + + /// + /// Tests + /// returns size given in constructor if not otherwise set + /// + /// + /// LUCENENET Note: this is equivalent to the testGetCorePoolSize or + /// testGetMaximumPoolSize methods in the Harmony tests, but we don't + /// have the same concepts or distinction, so just testing to make + /// sure that the maximum concurrency level is set correctly. + /// + [Test] + public void TestMaximumConcurrencyLevel() + { + TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1); + assertEquals(1, p1.MaximumConcurrencyLevel); + joinPool(p1); + } + + // LUCENENET NOTE: testGetKeepAliveTime, testGetThreadFactory, testSetThreadFactory, + // testSetThreadFactoryNull, testGetRejectedExecutionHandler, testSetRejectedExecutionHandler, + // testSetRejectedExecutionHandlerNull, testGetLargestPoolSize, and testGetPoolSize omitted; they are not relevant + + /// + /// getTaskCount increases, but doesn't overestimate, when tasks submitted + /// + /// + /// LUCENENET Note: GetTaskCount is provided in + /// to emulate the behavior of the Java method; it is not in the public + /// API for . This + /// just helps ensure the class is working as expected. + /// + [Test] + public void TestGetTaskCount() + { + TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1); + + try + { + assertEquals(0, p1.GetTaskCount()); + p1.Execute(MediumRunnable); + Thread.Sleep(SHORT_DELAY_MS); + assertEquals(1, p1.GetTaskCount()); + } + catch (Exception /*e*/) + { + unexpectedException(); + } + + joinPool(p1); + } + + /// + /// is false before shutdown, true after + /// + [Test] + public void TestIsShutdown() + { + var p1 = new LimitedConcurrencyLevelTaskScheduler(1); + assertFalse(p1.IsShutdown); + p1.Shutdown(); // LUCENENET NOTE: not catching SecurityException because that's not relevant here + assertTrue(p1.IsShutdown); + joinPool(p1); + } + + /// + /// isTerminated is false before termination, true after + /// + /// + /// LUCENENET Note: IsTerminated is provided in + /// to emulate the behavior of the Java method; it is not in the public + /// API for . This + /// just helps ensure the class is working as expected. + /// + [Test] + public void TestIsTerminated() + { + TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1); + assertFalse(p1.IsTerminated()); + + try + { + p1.Execute(MediumRunnable); + } + finally + { + p1.Shutdown(); // LUCENENET NOTE: not catching SecurityException because that's not relevant here + } + + try + { + assertTrue(p1.AwaitTermination(TimeSpan.FromMilliseconds(LONG_DELAY_MS))); + assertTrue(p1.IsTerminated()); + } + catch (Exception /*e*/) + { + unexpectedException(); + } + } + + // LUCENENET NOTE: remainder of methods omitted, could be added as needed. + } +} diff --git a/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs b/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs index 0717e957f6..07b236ceae 100644 --- a/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs +++ b/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs @@ -1,50 +1,50 @@ /* MICROSOFT LIMITED PUBLIC LICENSE version 1.1 -This license governs use of code marked as "sample" or "example" available on this web site -without a license agreement, as provided under the section above titled -"NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE." If you use such -code (the "software"), you accept this license. If you do not accept the +This license governs use of code marked as "sample" or "example" available on this web site +without a license agreement, as provided under the section above titled +"NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE." If you use such +code (the "software"), you accept this license. If you do not accept the license, do not use the software. 1. Definitions -The terms "reproduce," "reproduction," "derivative works," and "distribution" have the +The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor’s patent claims that read directly on its contribution. 2. Grant of Rights -(A) Copyright Grant - Subject to the terms of this license, including the license conditions -and limitations in section 3, each contributor grants you a non-exclusive, worldwide, -royalty-free copyright license to reproduce its contribution, prepare derivative works +(A) Copyright Grant - Subject to the terms of this license, including the license conditions +and limitations in section 3, each contributor grants you a non-exclusive, worldwide, +royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. -(B) Patent Grant - Subject to the terms of this license, including the license conditions -and limitations in section 3, each contributor grants you a non-exclusive, worldwide, -royalty-free license under its licensed patents to make, have made, use, sell, -offer for sale, import, and/or otherwise dispose of its contribution in the +(B) Patent Grant - Subject to the terms of this license, including the license conditions +and limitations in section 3, each contributor grants you a non-exclusive, worldwide, +royalty-free license under its licensed patents to make, have made, use, sell, +offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 3. Conditions and Limitations -(A) No Trademark License- This license does not grant you rights to use any contributors’ +(A) No Trademark License- This license does not grant you rights to use any contributors’ name, logo, or trademarks. -(B) If you bring a patent claim against any contributor over patents that you claim are -infringed by the software, your patent license from such contributor to the software +(B) If you bring a patent claim against any contributor over patents that you claim are +infringed by the software, your patent license from such contributor to the software ends automatically. -(C) If you distribute any portion of the software, you must retain all copyright, patent, +(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. -(D) If you distribute any portion of the software in source code form, you may do so only -under this license by including a complete copy of this license with your distribution. -If you distribute any portion of the software in compiled or object code form, you may +(D) If you distribute any portion of the software in source code form, you may do so only +under this license by including a complete copy of this license with your distribution. +If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. -(E) The software is licensed "as-is." You bear the risk of using it. The contributors -give no express warranties, guarantees or conditions. You may have additional consumer -rights under your local laws which this license cannot change. To the extent permitted -under your local laws, the contributors exclude the implied warranties of merchantability, +(E) The software is licensed "as-is." You bear the risk of using it. The contributors +give no express warranties, guarantees or conditions. You may have additional consumer +rights under your local laws which this license cannot change. To the extent permitted +under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. -(F) Platform Limitation - The licenses granted in sections 2(A) and 2(B) extend only -to the software or derivative works that you create that run directly on a Microsoft -Windows operating system product, Microsoft run-time technology (such as the .NET -Framework or Silverlight), or Microsoft application platform (such as Microsoft +(F) Platform Limitation - The licenses granted in sections 2(A) and 2(B) extend only +to the software or derivative works that you create that run directly on a Microsoft +Windows operating system product, Microsoft run-time technology (such as the .NET +Framework or Silverlight), or Microsoft application platform (such as Microsoft Office or Microsoft Dynamics). */ @@ -57,9 +57,9 @@ to the software or derivative works that you create that run directly on a Micro namespace Lucene.Net.Support.Threading { /// - /// Provides a task scheduler that ensures a maximum concurrency level while + /// Provides a task scheduler that ensures a maximum concurrency level while /// running on top of the thread pool. - /// + /// /// Source: https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler(v=vs.110).aspx /// internal class LimitedConcurrencyLevelTaskScheduler : TaskScheduler @@ -70,29 +70,29 @@ internal class LimitedConcurrencyLevelTaskScheduler : TaskScheduler [ThreadStatic] private static bool _currentThreadIsProcessingItems; - // The list of tasks to be executed + // The list of tasks to be executed private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) - // The maximum concurrency level allowed by this scheduler. + // The maximum concurrency level allowed by this scheduler. private readonly int _maxDegreeOfParallelism; - // Indicates whether the scheduler is currently processing work items. + // Indicates whether the scheduler is currently processing work items. private int _delegatesQueuedOrRunning = 0; - // Creates a new instance with the specified degree of parallelism. + // Creates a new instance with the specified degree of parallelism. public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) { if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism)); _maxDegreeOfParallelism = maxDegreeOfParallelism; } - // Queues a task to the scheduler. + // Queues a task to the scheduler. protected sealed override void QueueTask(Task task) { // Don't queue any more work. if (shutDown) return; - // Add the task to the list of tasks to be processed. If there aren't enough + // Add the task to the list of tasks to be processed. If there aren't enough // delegates currently queued or running to process tasks, schedule another. UninterruptableMonitor.Enter(_tasks); try @@ -110,15 +110,10 @@ protected sealed override void QueueTask(Task task) } } - // Inform the ThreadPool that there's work to be executed for this scheduler. + // Inform the ThreadPool that there's work to be executed for this scheduler. private void NotifyThreadPoolOfPendingWork() { -#if FEATURE_THREADPOOL_UNSAFEQUEUEWORKITEM - ThreadPool.UnsafeQueueUserWorkItem( -#else - ThreadPool.QueueUserWorkItem( -#endif - _ => + ThreadPool.UnsafeQueueUserWorkItem(_ => { // Note that the current thread is now processing work items. // This is necessary to enable inlining of tasks into this thread. @@ -158,7 +153,7 @@ private void NotifyThreadPoolOfPendingWork() }, null); } - // Attempts to execute the specified task on the current thread. + // Attempts to execute the specified task on the current thread. protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { // If this thread isn't already processing a task, we don't support inlining @@ -166,7 +161,7 @@ protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPrevi // If the task was previously queued, remove it from the queue if (taskWasPreviouslyQueued) - // Try to run the task. + // Try to run the task. if (TryDequeue(task)) return base.TryExecuteTask(task); else @@ -175,7 +170,7 @@ protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPrevi return base.TryExecuteTask(task); } - // Attempt to remove a previously scheduled task from the scheduler. + // Attempt to remove a previously scheduled task from the scheduler. protected sealed override bool TryDequeue(Task task) { UninterruptableMonitor.Enter(_tasks); @@ -189,10 +184,10 @@ protected sealed override bool TryDequeue(Task task) } } - // Gets the maximum concurrency level supported by this scheduler. + // Gets the maximum concurrency level supported by this scheduler. public sealed override int MaximumConcurrencyLevel => _maxDegreeOfParallelism; - // Gets an enumerable of the tasks currently scheduled on this scheduler. + // Gets an enumerable of the tasks currently scheduled on this scheduler. protected sealed override IEnumerable GetScheduledTasks() { bool lockTaken = false; @@ -208,10 +203,17 @@ protected sealed override IEnumerable GetScheduledTasks() } } - // Stops this TaskScheduler from queuing new tasks. + /// + /// Stops this TaskScheduler from queuing new tasks. + /// public void Shutdown() { shutDown.Value = true; } + + /// + /// Gets a value indicating whether this TaskScheduler has been shut down. + /// + public bool IsShutdown => shutDown; } }