{"record":{"id":"52ddfb0279578611","repo":"HangfireIO/Hangfire","slug":"all-the-threads-should-be-non-null-and-in-the-thre","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/BackgroundDispatcher.cs","lineNumber":64,"sourceCode":"\n            _execution = execution ?? throw new ArgumentNullException(nameof(execution));\n            _action = action ?? throw new ArgumentNullException(nameof(action));\n            _state = state;\n\n#if !NETSTANDARD1_3\n            AppDomainUnloadMonitor.EnsureInitialized();\n#endif\n\n            var threads = threadFactory(DispatchLoop)?.ToArray();\n\n            if (threads == null || threads.Length == 0)\n            {\n                throw new ArgumentException(\"At least one unstarted 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            _stopped = new CountdownEvent(threads.Length);\n\n            foreach (var thread in threads)\n            {\n                thread.Start();\n            }\n        }\n\n        public bool Wait(TimeSpan timeout)\n        {\n            return _stopped.WaitHandle.WaitOne(timeout);\n        }\n\n        public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken)\n        {\n            await _stopped.WaitHandle.WaitOneAsync(timeout, cancellationToken).ConfigureAwait(false);","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/HangfireIO/Hangfire/blob/c236dd0f930f831ec151e436e138ddc429a02a72/src/Hangfire.Core/Processing/BackgroundDispatcher.cs#L46-L82","documentation":"Thrown by the BackgroundDispatcher constructor (internal) when the threadFactory callback returns at least one thread that is null or whose ThreadState does not include Unstarted. The dispatcher owns thread lifecycle: it calls thread.Start() itself (line 71), so handing it an already-started or null thread violates the ownership contract and would double-start or NRE.","triggerScenarios":"Constructing BackgroundDispatcher with a Func<ThreadStart, IEnumerable<Thread>> whose result contains a null element or a thread already started elsewhere (ThreadState has the Unstarted bit cleared). Reached indirectly through Hangfire server wiring that supplies a custom thread factory.","commonSituations":"A custom server/hosting extension builds dedicated dispatcher threads and accidentally calls thread.Start() before returning them; a factory reuses/caches a Thread instance that was previously started; a factory returns null in place of a thread on some code path.","solutions":["Make the thread factory return only freshly-created Thread objects created with `new Thread(start)` and never call Start() on them.","Filter out nulls before returning: `threads.Where(t => t != null)` and ensure the result is non-empty.","If reusing Hangfire's pattern, copy DefaultThreadFactory from BackgroundTaskScheduler which constructs unstarted background threads."],"exampleFix":"// before\nFunc<ThreadStart, IEnumerable<Thread>> factory = start =>\n    new[] { new Thread(start) { IsBackground = true } };\nvar t = new Thread(factory.First()); t.Start();\nreturn new[] { t }; // already started -> throws\n\n// after\nFunc<ThreadStart, IEnumerable<Thread>> factory = start =>\n    new[] { new Thread(start) { IsBackground = true, Name = \"Worker\" } }; // unstarted","handlingStrategy":"validation","validationCode":"// Validate a thread factory's output is all non-null and unstarted before passing it on.\nbool IsValidFactory(Func<ThreadStart, IEnumerable<Thread>> factory, ThreadStart start)\n{\n    var threads = factory(start)?.ToArray();\n    return threads != null && threads.Length > 0 &&\n           threads.All(t => t != null && (t.ThreadState & System.Threading.ThreadState.Unstarted) != 0);\n}","typeGuard":"static bool IsUnstartedThread(Thread t) =>\n    t != null && (t.ThreadState & System.Threading.ThreadState.Unstarted) != 0;","tryCatchPattern":"try { var d = new BackgroundDispatcher(exec, action, state, factory); }\ncatch (ArgumentException ex) when (ex.ParamName == nameof(factory))\n{ /* log and rebuild factory to return fresh unstarted threads */ }","preventionTips":["Never call Thread.Start() inside a thread factory — let the dispatcher own lifecycle.","Always create threads with `new Thread(start)` and set IsBackground/Name only.","Filter nulls out of the returned collection."],"tags":["hangfire","threading","argument-validation","background-processing"],"backgroundTag":null,"analyzedSha":"c236dd0f930f831ec151e436e138ddc429a02a72","analyzedAt":"2026-08-13T20:27:11.027Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}