{"record":{"id":"4f9b17d76639e534","repo":"HangfireIO/Hangfire","slug":"all-the-threads-should-be-non-null-and-in-the-thre-4f9b17","errorCode":null,"errorMessage":"All the threads should be non-null and in the ThreadState.Unstarted state.","messagePattern":"All the threads should be non-null and in the ThreadState\\.Unstarted state\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Hangfire.Core/Processing/BackgroundTaskScheduler.cs","lineNumber":130,"sourceCode":"            _semaphore = new Semaphore(0, Int32.MaxValue);\n\n            // Stopped event should always be the first in this array, see the DispatchLoop method.\n            _waitHandles = new WaitHandle[] { _stopped, _semaphore };\n\n#if !NETSTANDARD1_3\n            AppDomainUnloadMonitor.EnsureInitialized();\n#endif\n\n            _threads = threadFactory(DispatchLoop)?.ToArray();\n\n            if (_threads == null || _threads.Length == 0)\n            {\n                throw new ArgumentException(\"At least one non-started thread should be created.\", nameof(threadFactory));\n            }\n\n            if (_threads.Any(static thread => thread == null || (thread.ThreadState & ThreadState.Unstarted) == 0))\n            {\n                throw new ArgumentException(\"All the threads should be non-null and in the ThreadState.Unstarted state.\", nameof(threadFactory));\n            }\n\n            foreach (var thread in _threads)\n            {\n                thread.Start();\n            }\n\n            _ourThreadIds = new HashSet<int>(_threads.Select(static x => x.ManagedThreadId));\n        }\n\n        /// <inheritdoc />\n        public override int MaximumConcurrencyLevel => _threads.Length;\n\n        /// <summary>Signals all the threads to be stopped and releases all the unmanaged resources.\n        /// This method should be called only when you are uninterested on the corresponding tasks,\n        /// i.e. during AppDomain unloads, process shutdowns, etc.</summary>\n        public void Dispose()\n        {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/HangfireIO/Hangfire/blob/c236dd0f930f831ec151e436e138ddc429a02a72/src/Hangfire.Core/Processing/BackgroundTaskScheduler.cs#L112-L148","documentation":"Thrown by the BackgroundTaskScheduler constructor when the threadFactory returns at least one thread that is null or not in the ThreadState.Unstarted state. The scheduler starts each thread itself (line 135) and records their ManagedThreadIds, so pre-started or null threads break ownership and inline-execution tracking.","triggerScenarios":"A threadFactory that calls thread.Start() before returning, returns a previously-started Thread, or includes a null element in the collection.","commonSituations":"Reusing a cached Thread that was already started; a factory that eagerly starts threads 'to be safe'; a conditional that inserts null for a skipped slot; copying a factory pattern but adding an inadvertent Start().","solutions":["Return only freshly-created, unstarted Thread objects (new Thread(start)) and never call Start() in the factory.","Filter nulls and ensure the collection stays non-empty afterwards.","Mirror BackgroundTaskScheduler.DefaultThreadFactory which sets Name/IsBackground but does not start."],"exampleFix":"// before\nFunc<ThreadStart, IEnumerable<Thread>> factory = start => {\n    var t = new Thread(start); t.Start(); return new[] { t }; // started -> throws\n};\n\n// after\nFunc<ThreadStart, IEnumerable<Thread>> factory = start =>\n    new[] { new Thread(start) { IsBackground = true, Name = \"BTS Worker\" } }; // unstarted","handlingStrategy":"validation","validationCode":"Func<ThreadStart, IEnumerable<Thread>> factory = start =>\n    Enumerable.Range(0, count)\n              .Select(i => new Thread(start) { IsBackground = true, Name = $\"Worker #{i}\" })\n              .Where(t => t != null); // never start, never return started threads","typeGuard":"static bool IsUnstarted(Thread t) =>\n    t != null && (t.ThreadState & System.Threading.ThreadState.Unstarted) != 0;","tryCatchPattern":"try { var s = new BackgroundTaskScheduler(factory, handler); }\ncatch (ArgumentException ex) when (ex.ParamName == nameof(threadFactory))\n{ /* rebuild factory without calling Start() */ }","preventionTips":["Never call Thread.Start() inside the factory.","Create fresh Thread objects per call; do not reuse started threads.","Filter nulls and keep the collection non-empty."],"tags":["hangfire","argument-validation","task-scheduler","threading"],"backgroundTag":null,"analyzedSha":"c236dd0f930f831ec151e436e138ddc429a02a72","analyzedAt":"2026-08-13T20:27:11.027Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}