< Summary

Information
Class: AmbientServices.Test.TestFifoTaskScheduler
Assembly: AmbientServices.Async.Test
File(s): /home/runner/work/AmbientServices.Async/AmbientServices.Async/AmbientServices.Async.Test/TestFifoTaskScheduler.cs
Tag: 76_25271648613
Line coverage
93%
Covered lines: 324
Uncovered lines: 23
Coverable lines: 347
Total lines: 719
Line coverage: 93.3%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
TestFifoTaskScheduler()100%11100%
FifoTaskScheduler_TaskInlined(...)100%11100%
UnhandledException()100%11100%
TaskInlinedEvent()100%1178.95%
RunWithStartNew()100%11100%
RunWithFunc()100%11100%
RunWithAction()100%1190%
RunFireAndForget()100%11100%
NoStatsStartNew()100%11100%
NoStatsRunWithAction()100%1187.5%
NoStatsFireAndForget()100%11100%
ExecuteWithCatchAndLog()100%11100%
Constructors()100%11100%
get_RecentUsage()100%11100%
set_RecentUsage(...)100%11100%
TooManyWorkers()100%11100%
ResetManyWorkers()100%1189.47%
ScaleDown()100%1193.75%
StartNewException()100%11100%
<StartNewException()100%11100%
DisposedException()100%11100%
UnobservedTaskException()100%11100%
StartTaskWithUnobservedException(...)100%11100%
UnhandledExceptionTracker(...)100%11100%
get_UnhandledExceptions()100%11100%
get_TaskSchedulerCount()100%11100%
get_FifoTaskSchedulerCount()100%11100%
TaskScheduler_UnobservedTaskException(...)100%11100%
FifoTaskScheduler_UnhandledException(...)100%11100%
ThrowExpectedUnobservedTaskException(...)100%11100%
ThrowExpectedException()100%11100%
RunAndUnwrapAggregateExceptions()100%210%
GetScheduledTasks()100%11100%
InvokeException()100%11100%
QueueTaskExceptions()100%11100%
Properties()100%11100%
IntrusiveSinglyLinkedList()100%11100%
get_Value()100%210%
set_Value(...)100%11100%
Worker()100%11100%
LongWait()100%11100%
WorkerNullWork()100%11100%
TestFifoTaskScheduler()100%11100%
AsyncLocalFlow()100%11100%
ExceptionHandling1()100%11100%
ExceptionHandling2()100%1190%
ExceptionHandling4()100%11100%
ExceptionHandling8()100%11100%
<ExceptionHandling8()100%11100%
ExceptionHandling9()100%11100%
<ExceptionHandling9()100%11100%
ExceptionHandling10()100%11100%
<ExceptionHandling10()100%11100%
ThrowExceptionAsyncTask()100%11100%
ThrowExceptionAsync()100%11100%
ThrowExceptionAsyncType<T>()100%11100%
ExpectExceptionTask()100%1180%
ExpectException(...)100%210%
ExpectException()100%1180%
StartNewAmbientThreadSchedulerSwitching()100%11100%
TransferWorkAmbientThreadSchedulerSwitching()100%11100%
<TransferWorkAmbientThreadSchedulerSwitching()100%11100%
VerifyTaskSchedulerRemainsCustom()100%11100%

File(s)

/home/runner/work/AmbientServices.Async/AmbientServices.Async/AmbientServices.Async.Test/TestFifoTaskScheduler.cs

#LineLine coverage
 1using AmbientServices;
 2using AmbientServices.Utilities;
 3using System;
 4using System.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Linq;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11#nullable enable
 12
 13namespace AmbientServices.Test
 14{
 15    [TestClass]
 16    public class TestFifoTaskScheduler
 17    {
 118        private static readonly AmbientService<IAmbientLogger> LoggerBackend = Ambient.GetService<IAmbientLogger>();
 119        private static readonly AmbientService<IAmbientStatistics> StatisticsBackend = Ambient.GetService<IAmbientStatis
 120        private static readonly AmbientService<IMockCpuUsage> MockCpu = Ambient.GetService<IMockCpuUsage>();
 21        private static int TasksInlined;
 22
 23        private static void FifoTaskScheduler_TaskInlined(object? sender, TaskInlinedEventArgs e)
 24        {
 125            Interlocked.Increment(ref TasksInlined);
 126        }
 27
 28        [TestMethod]
 29        public void UnhandledException()
 30        {
 131            Guid unique = Guid.NewGuid();
 132            UnhandledExceptionTracker tracker = new(unique);
 133            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(UnhandledException), priority: ThreadPrio
 34            try
 35            {
 136                FifoTaskScheduler.UnhandledException += tracker.FifoTaskScheduler_UnhandledException;
 137                scheduler.ExecuteAction(() => throw new ExpectedException(nameof(UnhandledException) + ":" + unique));
 138            }
 39            finally
 40            {
 141                FifoTaskScheduler.UnhandledException -= tracker.FifoTaskScheduler_UnhandledException;
 142            }
 143            Assert.AreEqual(1, tracker.UnhandledExceptions);
 144            Assert.AreEqual(1, tracker.FifoTaskSchedulerCount);
 145        }
 46        [TestMethod]
 47        public void TaskInlinedEvent()
 48        {
 49            try
 50            {
 151                FifoTaskScheduler.TaskInlined += FifoTaskScheduler_TaskInlined;
 52                // force a task to run inline
 153                using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(TaskInlinedEvent), 1, 2, 10);
 154                FifoTaskFactory factory = new(scheduler);
 155                List<Task> tasks = new();
 156                CancellationTokenSource cts = new();
 157                for (int i = 0; i < 2000; ++i)
 58                {
 159                    FakeWork w = new(i, true);
 160                    tasks.Add(factory.StartNew(() =>
 161                    {
 162                        if (TasksInlined > 0) { cts.Cancel(); return Task.CompletedTask; }
 063                        return w.DoDelayOnlyWorkAsync(CancellationToken.None).AsTask();
 164                    }, CancellationToken.None, TaskCreationOptions.None, scheduler));
 65                }
 166                Task.WaitAll(tasks.ToArray());
 167                if (TasksInlined > 0) return;   // this works too!
 068            }
 069            catch (OperationCanceledException)
 70            {
 71                // we expect to get canceled for this test!
 072                return;
 73            }
 74            finally
 75            {
 176                FifoTaskScheduler.TaskInlined -= FifoTaskScheduler_TaskInlined;
 177            }
 078            Assert.Fail("Unable to force task to run inline!");
 179        }
 80
 81        [TestMethod]
 82        public async Task RunWithStartNew()
 83        {
 84            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 185            List<Task<Task>> tasks = new();
 186            for (int i = 0; i < 1000; ++i)
 87            {
 188                FakeWork w = new(i, true);
 189                tasks.Add(FifoTaskFactory.Default.StartNew(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));
 90            }
 191            await Task.WhenAll(tasks.ToArray());
 192            foreach (Task<Task> task in tasks)
 93            {
 194                await task.Result;
 95            }
 196            FifoTaskScheduler.Default.Reset();
 197        }
 98        [TestMethod]
 99        public async Task RunWithFunc()
 100        {
 101            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 1102            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(RunWithFunc), priority: ThreadPriority.Hi
 1103            List<Task<Task>> tasks = new();
 1104            for (int i = 0; i < 1000; ++i)
 105            {
 1106                FakeWork w = new(i, true);
 1107                tasks.Add(scheduler.Run(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));
 108            }
 1109            Task.WaitAll(tasks.ToArray());
 1110            foreach (Task<Task> task in tasks)
 111            {
 1112                await task.Result;
 113            }
 1114            scheduler.Reset();
 1115        }
 116        [TestMethod]
 117        public async Task RunWithAction()
 118        {
 119            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 1120            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(RunWithAction), priority: ThreadPriority.
 1121            ConcurrentBag<Task> tasks = new();
 1122            for (int i = 0; i < 1000; ++i)
 123            {
 1124                FakeWork w = new(i, true);
 1125                tasks.Add(scheduler.Run(() => { tasks.Add(w.DoMixedWorkAsync(CancellationToken.None).AsTask()); }));
 126            }
 1127            while (tasks.Count < 1000)
 128            {
 0129                await Task.Delay(25);
 130            }
 1131            Task.WaitAll(tasks.ToArray());
 1132            scheduler.Reset();
 1133        }
 134        [TestMethod]
 135        public async Task RunFireAndForget()
 136        {
 137            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 1138            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(RunFireAndForget), priority: ThreadPriori
 1139            ConcurrentBag<Task> tasks = new();
 1140            for (int i = 0; i < 1000; ++i)
 141            {
 1142                FakeWork w = new(i, true);
 1143                scheduler.FireAndForget(() => tasks.Add(w.DoMixedWorkAsync(CancellationToken.None).AsTask()));
 144            }
 1145            while (tasks.Count < 1000)
 146            {
 1147                await Task.Delay(25);
 148            }
 1149            Task.WaitAll(tasks.ToArray());
 1150            scheduler.Reset();
 1151        }
 152        [TestMethod]
 153        public async Task NoStatsStartNew()
 154        {
 1155            using IDisposable d = StatisticsBackend.ScopedLocalOverride(null);
 1156            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(NoStatsStartNew), priority: ThreadPriorit
 1157            FifoTaskFactory testFactory = new(scheduler);
 1158            List<Task<Task>> tasks = new();
 1159            tasks.Add(scheduler.Run(() => new FakeWork(-1, true).DoMixedWorkAsync(CancellationToken.None).AsTask()));   
 1160            for (int i = 0; i < 100; ++i)
 161            {
 1162                FakeWork w = new(i, true);
 1163                tasks.Add(testFactory.StartNew(() => w.DoDelayOnlyWorkAsync(CancellationToken.None).AsTask()));
 164            }
 1165            Task.WaitAll(tasks.ToArray());
 1166            foreach (Task<Task> task in tasks)
 167            {
 1168                await task.Result;
 169            }
 1170            await scheduler.Run(() => new ValueTask());
 1171            scheduler.Reset();
 1172        }
 173        [TestMethod]
 174        public async Task NoStatsRunWithAction()
 175        {
 1176            using IDisposable d = StatisticsBackend.ScopedLocalOverride(null);
 1177            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(NoStatsRunWithAction), priority: ThreadPr
 1178            FifoTaskFactory testFactory = new(scheduler);
 1179            Task? task = null;
 1180            await scheduler.Run(() => { task = new FakeWork(1, true).DoDelayOnlyWorkAsync(CancellationToken.None).AsTask
 1181            while (task == null)
 182            {
 0183                await Task.Delay(25);
 184            }
 1185        }
 186        [TestMethod]
 187        public async Task NoStatsFireAndForget()
 188        {
 1189            using IDisposable d = StatisticsBackend.ScopedLocalOverride(null);
 1190            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(NoStatsFireAndForget), priority: ThreadPr
 1191            FifoTaskFactory testFactory = new(scheduler);
 1192            Task? task = null;
 1193            scheduler.FireAndForget(() => task = new FakeWork(1, true).DoDelayOnlyWorkAsync(CancellationToken.None).AsTa
 1194            while (task == null)
 195            {
 1196                await Task.Delay(25);
 197            }
 1198        }
 199        [TestMethod]
 200        public void ExecuteWithCatchAndLog()
 201        {
 1202            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExecuteWithCatchAndLog));
 1203            scheduler.ExecuteWithCatchAndLog(() => throw new ExpectedException());
 1204            scheduler.ExecuteWithCatchAndLog(() => throw new TaskCanceledException());
 205            //scheduler.ExecuteWithCatchAndLog(() => throw new ThreadAbortException()); // can't construct this, so can'
 1206        }
 207        [TestMethod]
 208        public void Constructors()
 209        {
 210            FifoTaskFactory testFactory;
 1211            testFactory = new(CancellationToken.None);
 1212            testFactory = new(FifoTaskScheduler.Default);
 1213            testFactory = new(TaskCreationOptions.None, TaskContinuationOptions.None);
 1214        }
 215        class MockCpuUsage : IMockCpuUsage
 216        {
 1217            public float RecentUsage { get; set; }
 218        }
 219        [TestMethod]
 220        public void TooManyWorkers()
 221        {
 1222            MockCpuUsage mockCpu = new();
 1223            using IDisposable d = MockCpu.ScopedLocalOverride(mockCpu);
 1224            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(TooManyWorkers), 1, 2, 10);
 1225            Assert.IsGreaterThan(0, scheduler.ReadyWorkers);
 1226            Assert.AreEqual(0, scheduler.BusyWorkers);
 1227            FifoTaskFactory factory = new(scheduler);
 1228            List<Task> tasks = new();
 229            // the scheduler should think the CPU is very high, so it should enter the crazy usage notify
 1230            mockCpu.RecentUsage = 1.0f;
 1231            for (int i = 0; i < 20; ++i)
 232            {
 1233                FakeWork w = new(i, true);
 1234                tasks.Add(factory.StartNew(() => w.DoDelayOnlyWorkAsync(CancellationToken.None).AsTask(), CancellationTo
 235            }
 1236            Task.WaitAll(tasks.ToArray());
 1237            scheduler.Reset();
 1238        }
 239        [TestMethod]
 240        public async Task ResetManyWorkers()
 241        {
 1242            MockCpuUsage mockCpu = new();
 1243            using IDisposable s = StatisticsBackend.ScopedLocalOverride(null);
 1244            using IDisposable c = MockCpu.ScopedLocalOverride(mockCpu);
 1245            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ResetManyWorkers), 1, 100, 10);
 1246            DateTime lastScaleUp = scheduler.LastScaleUp;
 247            Debug.Assert(DateTime.UtcNow > lastScaleUp);
 1248            DateTime lastScaleDown = scheduler.LastScaleDown;
 249            Debug.Assert(DateTime.UtcNow > lastScaleDown);
 1250            DateTime lastReset = scheduler.LastResetTime;
 251            Debug.Assert(DateTime.UtcNow > lastReset);
 1252            ConcurrentBag<Task> tasks = new();
 253            int i;
 1254            for (i = 0; i < 250 && scheduler.ReadyWorkers < 3; ++i)
 255            {
 1256                FakeWork w = new(i, true);
 1257                tasks.Add(scheduler.Run(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));       // note that 
 258            }
 259            // reset the scheduler
 1260            scheduler.Reset();
 261
 1262            Task.WaitAll(tasks.ToArray());
 1263            Assert.IsTrue(scheduler.LastScaleUp > lastScaleUp, $"No scale up, i={i}, threads={scheduler.Workers}");
 264
 265            // wait for a while for the reset to finish
 1266            while (scheduler.LastResetTime <= lastReset && DateTime.UtcNow < lastReset.AddSeconds(30))
 267            {
 268                // reset the scheduler again just in case
 0269                scheduler.Reset();
 0270                await Task.Delay(50);
 271            }
 1272            Assert.IsTrue(scheduler.LastResetTime > lastReset, $"No reset, {scheduler.LastResetTime} vs {lastReset} vs {
 1273        }
 274        [TestMethod]
 275        public async Task ScaleDown()
 276        {
 1277            MockCpuUsage mockCpu = new();
 1278            using IDisposable d = MockCpu.ScopedLocalOverride(mockCpu);     // install a mock CPU so that the scheduler 
 1279            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ScaleDown), 1, 25, 10);
 1280            DateTime lastScaleUp = scheduler.LastScaleUp;
 281            Debug.Assert(DateTime.UtcNow > lastScaleUp);
 1282            DateTime lastScaleDown = scheduler.LastScaleDown;
 283            Debug.Assert(DateTime.UtcNow > lastScaleDown);
 1284            FifoTaskFactory factory = new(scheduler);
 1285            List<Task<Task>> tasks = new();
 286            int i;
 1287            for (i = 0; i < 100 && scheduler.Workers < 10; ++i)
 288            {
 1289                FakeWork w = new(i, true);
 1290                tasks.Add(scheduler.Run(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));       // note that 
 291            }
 1292            Task.WaitAll(tasks.ToArray());
 1293            Assert.IsTrue(scheduler.LastScaleUp > lastScaleUp, $"No scale up, threads={scheduler.Workers}");
 294            // now wait for a while for the master thread to scale things down
 1295            while (scheduler.LastScaleDown <= lastScaleDown && DateTime.UtcNow < lastScaleDown.AddSeconds(30))
 296            {
 0297                await Task.Delay(50);
 298            }
 1299            Assert.IsTrue(scheduler.LastScaleDown > lastScaleDown, $"No scale down, {scheduler.LastScaleDown} vs {lastSc
 1300        }
 301        [TestMethod]
 302        public async Task StartNewException()
 303        {
 304            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 1305            await Assert.ThrowsExactlyAsync<ExpectedException>(async () => await FifoTaskFactory.Default.StartNew(() => 
 1306        }
 307        [TestMethod]
 308        public void DisposedException()
 309        {
 310            FifoTaskFactory test;
 1311            using (FifoTaskScheduler testScheduler = FifoTaskScheduler.Start(nameof(DisposedException)))
 312            {
 1313                test = new(testScheduler);
 1314            }
 1315            Assert.Throws<TaskSchedulerException>(() => test.StartNew(() => { }));     // our code throws an ObjectDispo
 1316        }
 317
 318        [TestMethod]        // Note that this test will fail when run in isolation, possibly because the garbage collect
 319        public async Task UnobservedTaskException()
 320        {
 321            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 1322            Guid unique = Guid.NewGuid();
 1323            UnhandledExceptionTracker tracker = new(unique);
 324            try
 325            {
 1326                TaskScheduler.UnobservedTaskException += tracker.TaskScheduler_UnobservedTaskException;
 1327                FifoTaskScheduler.UnhandledException += tracker.TaskScheduler_UnobservedTaskException;
 1328                StartTaskWithUnobservedException(unique);
 1329                for (int loop = 0; loop < 100; ++loop)
 330                {
 1331                    if (tracker.UnhandledExceptions > 0) break;
 1332                    await Task.Delay(100);
 1333                    GC.Collect(2, GCCollectionMode.Forced, true, true);
 1334                    GC.WaitForPendingFinalizers();
 335                }
 1336                Assert.IsGreaterThanOrEqualTo(1, tracker.UnhandledExceptions);
 1337                Assert.IsGreaterThanOrEqualTo(1, tracker.TaskSchedulerCount);
 1338            }
 339            finally
 340            {
 1341                FifoTaskScheduler.UnhandledException -= tracker.TaskScheduler_UnobservedTaskException;
 1342                TaskScheduler.UnobservedTaskException -= tracker.TaskScheduler_UnobservedTaskException;
 343            }
 1344        }
 345        private static void StartTaskWithUnobservedException(Guid unique)
 346        {
 1347            _ = FifoTaskFactory.Default.StartNew(() => ThrowExpectedUnobservedTaskException(unique));
 1348        }
 349
 350        class UnhandledExceptionTracker
 351        {
 352            private Guid _unique;
 353            private int _unhandledExceptions;
 354            private int _taskSchedulerCount;
 355            private int _fifoTaskSchedulerCount;
 356
 1357            internal UnhandledExceptionTracker(Guid unique)
 358            {
 1359                _unique = unique;
 1360            }
 361
 1362            internal int UnhandledExceptions => _unhandledExceptions;
 1363            internal int TaskSchedulerCount => _taskSchedulerCount;
 1364            internal int FifoTaskSchedulerCount => _fifoTaskSchedulerCount;
 365
 366            internal void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
 367            {
 1368                Assert.AreEqual(typeof(AggregateException), e.Exception?.GetType());
 1369                Assert.AreEqual(typeof(ExpectedException), e.Exception?.InnerExceptions[0].GetType());
 1370                if ((e.Exception?.InnerExceptions[0] as ExpectedException)?.TestName?.Contains(_unique.ToString()) ?? fa
 1371                Interlocked.Increment(ref _taskSchedulerCount);
 1372                e.SetObserved();
 1373            }
 374            internal void FifoTaskScheduler_UnhandledException(object? sender, UnobservedTaskExceptionEventArgs e)
 375            {
 1376                Assert.AreEqual(typeof(AggregateException), e.Exception?.GetType());
 1377                Assert.AreEqual(typeof(ExpectedException), e.Exception?.InnerExceptions[0].GetType());
 1378                if ((e.Exception?.InnerExceptions[0] as ExpectedException)?.TestName?.Contains(_unique.ToString()) ?? fa
 1379                Interlocked.Increment(ref _fifoTaskSchedulerCount);
 1380                e.SetObserved();
 1381            }
 382        }
 383
 384        private static void ThrowExpectedUnobservedTaskException(Guid unique)
 385        {
 1386            throw new ExpectedException(nameof(UnobservedTaskException) + ":" + unique);
 387        }
 388        private static void ThrowExpectedException()
 389        {
 1390            throw new ExpectedException();
 391        }
 392        private static async Task RunAndUnwrapAggregateExceptions(Func<Task> f)
 393        {
 394            try
 395            {
 0396                await f();
 0397            }
 398            catch (AggregateException ex)
 399            {
 0400                Async.ConvertAggregateException(ex);
 0401                throw;
 402            }
 0403        }
 404        [TestMethod]
 405        public void GetScheduledTasks()
 406        {
 1407            IEnumerable<Task> tasks = FifoTaskScheduler.Default.GetScheduledTasksDirect();
 1408            Assert.AreEqual(0, tasks.Count());
 1409        }
 410        [TestMethod]
 411        public async Task InvokeException()
 412        {
 1413            await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => FifoTaskScheduler.Default.Run<int>(null!));
 1414            await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => FifoTaskScheduler.Default.TransferWork((Func<Va
 1415            await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => FifoTaskScheduler.Default.TransferWork((Func<Va
 1416            Assert.ThrowsExactly<ArgumentNullException>(() => FifoTaskScheduler.Default.Run(null!));
 1417            Assert.ThrowsExactly<ArgumentNullException>(() => FifoTaskScheduler.Default.FireAndForget(null!));
 1418            await Assert.ThrowsExactlyAsync<ExpectedException>(() => FifoTaskScheduler.Default.Run<int>(() => throw new 
 1419            await Assert.ThrowsExactlyAsync<ExpectedException>(() => FifoTaskScheduler.Default.Run(() => throw new Expec
 1420        }
 421        [TestMethod]
 422        public void QueueTaskExceptions()
 423        {
 1424            Assert.ThrowsExactly<ArgumentNullException>(() => FifoTaskScheduler.Default.QueueTaskDirect(null!));
 1425        }
 426        [TestMethod]
 427        public void Properties()
 428        {
 1429            Assert.IsGreaterThanOrEqualTo(0, FifoTaskScheduler.Default.Workers);
 1430            Assert.IsGreaterThanOrEqualTo(0, FifoTaskScheduler.Default.BusyWorkers);
 1431            Assert.IsGreaterThanOrEqualTo(Environment.ProcessorCount, FifoTaskScheduler.Default.MaximumConcurrencyLevel)
 1432        }
 433        [TestMethod]
 434        public void IntrusiveSinglyLinkedList()
 435        {
 1436            InterlockedSinglyLinkedList<NodeTest> list1 = new();
 1437            InterlockedSinglyLinkedList<NodeTest> list2 = new();
 1438            NodeTest node = new() { Value = 1 };
 1439            list1.Push(node);
 1440            Assert.ThrowsExactly<ArgumentNullException>(() => list2.Push(null!));
 1441            Assert.ThrowsExactly<InvalidOperationException>(() => list2.Push(node));
 442            list1.Validate();
 1443            Assert.AreEqual(1, list1.Count);
 1444            list1.Clear();
 1445            Assert.AreEqual(0, list1.Count);
 1446            list2.Push(node);
 1447            NodeTest? popped = list2.Pop();
 1448            Assert.AreEqual(node, popped);
 1449        }
 450        class NodeTest : IntrusiveSinglyLinkedListNode
 451        {
 1452            public int Value { get; set; }
 453        }
 454        [TestMethod]
 455        public async Task Worker()
 456        {
 1457            FifoTaskScheduler? scheduler = null;
 458            try
 459            {
 1460                scheduler = FifoTaskScheduler.Start(nameof(Worker));
 1461                FifoWorker worker = FifoWorker.Start(scheduler, "1", ThreadPriority.Normal);  // the worker disposes of 
 1462                worker.Invoke(LongWait);
 1463                Assert.IsTrue(worker.IsBusy);
 1464                Assert.ThrowsExactly<InvalidOperationException>(() => worker.Invoke(LongWait));
 1465                Assert.IsFalse(FifoWorker.IsWorkerInternalMethod(null));
 1466                Assert.IsFalse(FifoWorker.IsWorkerInternalMethod(typeof(TestFifoTaskScheduler).GetMethod(nameof(Worker))
 1467                Assert.IsTrue(FifoWorker.IsWorkerInternalMethod(typeof(FifoWorker).GetMethod("Invoke", System.Reflection
 1468                Assert.IsTrue(FifoWorker.IsWorkerInternalMethod(typeof(FifoWorker).GetMethod("WorkerFunc", System.Reflec
 1469                worker.Stop();
 1470                Assert.ThrowsExactly<InvalidOperationException>(() => worker.Invoke(null!));
 1471            }
 472            finally
 473            {
 1474                scheduler?.Dispose();
 475            }
 1476            Assert.ThrowsExactly<ObjectDisposedException>(() => scheduler.Run(() => { }));
 1477            Assert.ThrowsExactly<ObjectDisposedException>(() => scheduler.FireAndForget(() => { }));
 1478            await Assert.ThrowsExactlyAsync<ObjectDisposedException>(() => scheduler.Run(() => new ValueTask()));
 1479        }
 480        public void LongWait()
 481        {
 1482            Thread.Sleep(5000);
 1483        }
 484        [TestMethod]
 485        public void WorkerNullWork()
 486        {
 1487            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(WorkerNullWork));
 1488            FifoWorker worker = scheduler.CreateWorker();    // the worker disposes of itself
 1489            worker.Invoke(null!);
 1490        }
 1491        private AsyncLocal<int> ali = new();
 492        [TestMethod]
 493        public async Task AsyncLocalFlow()
 494        {
 1495            int testvalue = 48902343;
 1496            ali.Value = testvalue;
 1497            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(AsyncLocalFlow));
 1498            TaskCompletionSource<bool> completion = new();
 1499            await Task.Factory.StartNew(() =>
 1500            {
 1501                Assert.AreEqual(testvalue, ali.Value);
 1502            }, CancellationToken.None, TaskCreationOptions.None, scheduler);
 1503            await scheduler.Run(() => Assert.AreEqual(testvalue, ali.Value));
 1504        }
 505        [TestMethod]
 506        public async Task ExceptionHandling1()
 507        {
 1508            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling1));
 1509            scheduler.FireAndForget(() => throw new ExpectedException());
 1510            await Task.Delay(1000);
 1511        }
 512        [TestMethod]
 513        public async Task ExceptionHandling2()
 514        {
 1515            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling2));
 1516            Task? t = null;
 517            try
 518            {
 1519                t = ThrowExceptionAsyncTask();
 1520                await t;
 0521            }
 1522            catch
 523            {
 1524            }
 1525            scheduler.QueueTaskDirect(t!);
 1526            await Task.Delay(1000);
 1527        }
 528        [TestMethod]
 529        public async Task ExceptionHandling4()
 530        {
 1531            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling4));
 1532            await ExpectExceptionTask(() => scheduler.Run(() => throw new ExpectedException()));
 1533            await Task.Delay(1000);
 1534        }
 535        [TestMethod]
 536        public async Task ExceptionHandling8()
 537        {
 1538            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling8));
 1539            await ExpectException(async () => await scheduler.Run(() => throw new ExpectedException()));
 1540            await Task.Delay(1000);
 1541        }
 542        [TestMethod]
 543        public async Task ExceptionHandling9()
 544        {
 1545            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling9));
 1546            await ExpectException(async () => await scheduler.TransferWork(ThrowExceptionAsync));
 1547            await Task.Delay(1000);
 1548        }
 549        [TestMethod]
 550        public async Task ExceptionHandling10()
 551        {
 1552            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling10));
 1553            await ExpectException(async () => await scheduler.TransferWork(ThrowExceptionAsyncType<int>));
 1554            await Task.Delay(1000);
 1555        }
 556        private async Task ThrowExceptionAsyncTask()
 557        {
 1558            await Task.Delay(100);
 1559            throw new ExpectedException();
 560        }
 561        private ValueTask ThrowExceptionAsync()
 562        {
 1563            throw new ExpectedException();
 564        }
 565        private ValueTask<T> ThrowExceptionAsyncType<T>()
 566        {
 1567            throw new ExpectedException();
 568        }
 569        private async ValueTask ExpectExceptionTask(Func<Task> t)
 570        {
 571            try
 572            {
 1573                await t();
 0574                throw new InvalidOperationException();
 575            }
 1576            catch (ExpectedException)
 577            {
 578                // ok!
 1579            }
 1580        }
 581        private void ExpectException(Action a)
 582        {
 583            try
 584            {
 0585                a();
 0586                throw new InvalidOperationException();
 587            }
 0588            catch (ExpectedException)
 589            {
 590                // ok!
 0591            }
 0592        }
 593        private async ValueTask ExpectException(Func<ValueTask> a)
 594        {
 595            try
 596            {
 1597                await a();
 0598                throw new InvalidOperationException();
 599            }
 1600            catch (ExpectedException)
 601            {
 602                // ok!
 1603            }
 1604        }
 605        [TestMethod]
 606        public async Task StartNewAmbientThreadSchedulerSwitching()
 607        {
 1608            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(StartNewAmbientThreadSchedulerSwitching))
 1609            TaskCompletionSource<bool> completion = new();
 1610            await Task.Factory.StartNew(() => VerifyTaskSchedulerRemainsCustom(), CancellationToken.None, TaskCreationOp
 1611        }
 612        [TestMethod]
 613        public async Task TransferWorkAmbientThreadSchedulerSwitching()
 614        {
 1615            await FifoTaskScheduler.Default.TransferWork(async () =>
 1616            {
 1617                await VerifyTaskSchedulerRemainsCustom();
 1618            });
 1619        }
 620
 621        private async Task VerifyTaskSchedulerRemainsCustom()
 622        {
 1623            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 1624            await Task.Yield();
 1625            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 1626            await Task.Delay(100);
 1627            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 1628            await Task.Delay(100).ConfigureAwait(true);
 1629            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 630
 631            // ... more arbitrary async processing
 632
 1633            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 1634        }
 635    }
 636    public class FakeWork
 637    {
 638        private readonly bool _fast;
 639        private readonly long _id;
 640
 641        public FakeWork(long id, bool fast)
 642        {
 643            _fast = fast;
 644            _id = id;
 645        }
 646
 647        public async ValueTask DoMixedWorkAsync(CancellationToken cancel = default, bool verifyHPThread = true)
 648        {
 649            ulong hash = GetHash(_id);
 650            await Task.Yield();
 651            //string? threadName = Thread.CurrentThread.Name;
 652
 653            if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 654            Stopwatch s = Stopwatch.StartNew();
 655            for (int outer = 0; outer < (int)(hash % 256) && !cancel.IsCancellationRequested; ++outer)
 656            {
 657                Stopwatch cpu = Stopwatch.StartNew();
 658                // use some CPU
 659                for (int spin = 0; spin < (int)((hash >> 6) % (_fast ? 16UL : 256UL)); ++spin)
 660                {
 661                    double d1 = 0.0000000000000001;
 662                    double d2 = 0.0000000000000001;
 663                    for (int inner = 0; inner < (_fast ? 100 : 1000000); ++inner) { d2 = d1 * d2; }
 664                }
 665                cpu.Stop();
 666                if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 667                Stopwatch mem = Stopwatch.StartNew();
 668                // use some memory
 669                int bytesPerLoop = (int)((hash >> 12) % (_fast ? 10UL : 1024UL));
 670                int loops = (int)((hash >> 22) % 1024);
 671                for (int memory = 0; memory < loops; ++memory)
 672                {
 673                    byte[] bytes = new byte[bytesPerLoop];
 674                }
 675                mem.Stop();
 676                if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 677                Stopwatch io = Stopwatch.StartNew();
 678                // simulate I/O by blocking
 679                await Task.Delay((int)((hash >> 32) % (_fast ? 5UL : 500UL)), cancel);
 680                io.Stop();
 681                if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 682            }
 683            if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 684            //Debug.WriteLine($"Ran work {_id} on {threadName}!", "Work");
 685        }
 686        public async ValueTask DoDelayOnlyWorkAsync(CancellationToken cancel = default)
 687        {
 688            ulong hash = GetHash(_id);
 689            await Task.Yield();
 690            //string? threadName = Thread.CurrentThread.Name;
 691
 692            Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 693            Stopwatch s = Stopwatch.StartNew();
 694            for (int outer = 0; outer < (int)(hash % 256) && !cancel.IsCancellationRequested; ++outer)
 695            {
 696                Stopwatch io = Stopwatch.StartNew();
 697                // simulate I/O by blocking
 698                await Task.Delay((int)((hash >> 32) % (_fast ? 5UL : 500UL)), cancel);
 699                io.Stop();
 700                Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 701            }
 702            Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 703            //Debug.WriteLine($"Ran work {_id} on {threadName}!", "Work");
 704        }
 705        private static ulong GetHash(long id)
 706        {
 707            unchecked
 708            {
 709                ulong x = (ulong)id * 1_111_111_111_111_111_111UL;        // note that this is a prime number (but not a
 710                x = (((x & 0xaaaaaaaaaaaaaaaa) >> 1) | ((x & 0x5555555555555555) << 1));
 711                x = (((x & 0xcccccccccccccccc) >> 2) | ((x & 0x3333333333333333) << 2));
 712                x = (((x & 0xf0f0f0f0f0f0f0f0) >> 4) | ((x & 0x0f0f0f0f0f0f0f0f) << 4));
 713                x = (((x & 0xff00ff00ff00ff00) >> 8) | ((x & 0x00ff00ff00ff00ff) << 8));
 714                x = (((x & 0xffff0000ffff0000) >> 16) | ((x & 0x0000ffff0000ffff) << 16));
 715                return ((x >> 32) | (x << 32));
 716            }
 717        }
 718    }
 719}

Methods/Properties

TestFifoTaskScheduler()
FifoTaskScheduler_TaskInlined(object, AmbientServices.TaskInlinedEventArgs)
UnhandledException()
TaskInlinedEvent()
RunWithStartNew()
RunWithFunc()
RunWithAction()
RunFireAndForget()
NoStatsStartNew()
NoStatsRunWithAction()
NoStatsFireAndForget()
ExecuteWithCatchAndLog()
Constructors()
get_RecentUsage()
set_RecentUsage(float)
TooManyWorkers()
ResetManyWorkers()
ScaleDown()
StartNewException()
<StartNewException()
DisposedException()
UnobservedTaskException()
StartTaskWithUnobservedException(System.Guid)
UnhandledExceptionTracker(System.Guid)
get_UnhandledExceptions()
get_TaskSchedulerCount()
get_FifoTaskSchedulerCount()
TaskScheduler_UnobservedTaskException(object, System.Threading.Tasks.UnobservedTaskExceptionEventArgs)
FifoTaskScheduler_UnhandledException(object, System.Threading.Tasks.UnobservedTaskExceptionEventArgs)
ThrowExpectedUnobservedTaskException(System.Guid)
ThrowExpectedException()
RunAndUnwrapAggregateExceptions()
GetScheduledTasks()
InvokeException()
QueueTaskExceptions()
Properties()
IntrusiveSinglyLinkedList()
get_Value()
set_Value(int)
Worker()
LongWait()
WorkerNullWork()
TestFifoTaskScheduler()
AsyncLocalFlow()
ExceptionHandling1()
ExceptionHandling2()
ExceptionHandling4()
ExceptionHandling8()
<ExceptionHandling8()
ExceptionHandling9()
<ExceptionHandling9()
ExceptionHandling10()
<ExceptionHandling10()
ThrowExceptionAsyncTask()
ThrowExceptionAsync()
ThrowExceptionAsyncType<T>()
ExpectExceptionTask()
ExpectException(System.Action)
ExpectException()
StartNewAmbientThreadSchedulerSwitching()
TransferWorkAmbientThreadSchedulerSwitching()
<TransferWorkAmbientThreadSchedulerSwitching()
VerifyTaskSchedulerRemainsCustom()