{"record":{"id":"e963cdde28d3548e","repo":"stride3d/stride","slug":"trying-to-lock-while-another-thread-owns-the-lock","errorCode":null,"errorMessage":"Trying to lock while another thread owns the lock.","messagePattern":"Trying to lock while another thread owns the lock\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.Design/MicroThreadLock.cs","lineNumber":199,"sourceCode":"        {\n        }\n\n        public override void Dispose()\n        {\n            Monitor.Exit(MicroThreadLock.syncLock);\n            base.Dispose();\n        }\n\n        internal override void Reenter()\n        {\n            Monitor.Enter(MicroThreadLock.syncLock);\n            base.Reenter();\n        }\n\n        internal void Take()\n        {\n            if (MicroThreadLock.currentSyncLockThread != 0)\n                throw new InvalidOperationException(\"Trying to lock while another thread owns the lock.\");\n\n            MicroThreadLock.currentSyncLockThread = Environment.CurrentManagedThreadId;\n            MicroThreadLock.currentSyncLock = this;\n        }\n\n        internal override void Release()\n        {\n            if (MicroThreadLock.currentSyncLockThread != Environment.CurrentManagedThreadId)\n                throw new InvalidOperationException(\"Trying to unlock while another thread owns the lock.\");\n\n            MicroThreadLock.currentSyncLockThread = 0;\n            MicroThreadLock.currentSyncLock = null;\n        }\n\n        public IDisposable Lock()\n        {\n            if (!locked)\n            {","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.Design/MicroThreadLock.cs#L181-L217","documentation":"SyncLock.Take assigns the current managed thread id to the static currentSyncLockThread, establishing single-thread ownership of the sync lock. If another thread already owns it (currentSyncLockThread != 0), taking it again would silently steal ownership, so it throws InvalidOperationException.","triggerScenarios":"Calling Take (via Lock()) from a second thread while a first thread still holds the sync lock; concurrent Lock() calls on the same SyncLock from different threads without waiting for release.","commonSituations":"Background threads or ThreadPool work items entering a Lock() section while the UI/main thread holds it; parallel loops touching the same SyncLock; forgetting that SyncLock is single-owner, unlike the queued MicroThreadLock.","solutions":["Serialize access yourself (e.g. SemaphoreSlim(1,1) or a monitor) so only one thread calls Lock() at a time.","Ensure the owning thread's using scope exits before other threads attempt Lock().","Use the queued MicroThreadLock variant instead of SyncLock if cross-thread waiting is needed.","Wrap Lock() in try/catch (InvalidOperationException) to detect cross-thread contention and retry later."],"exampleFix":"// before\nParallel.For(0, 10, i => { using (syncLock.Lock()) { /* work */ } }); // throws\n// after\nvar gate = new SemaphoreSlim(1, 1);\nParallel.For(0, 10, async i => { await gate.WaitAsync(); try { using (syncLock.Lock()) { /* work */ } } finally { gate.Release(); } });","handlingStrategy":"try-catch","validationCode":"// Only call Lock() on the owning thread, or gate access:\nif (SyncLockIsFree()) syncLock.Lock(); // expose/track currentSyncLockThread via your own state","typeGuard":null,"tryCatchPattern":"try { using (syncLock.Lock()) { /* work */ } }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"another thread owns the lock\")) { /* defer or retry on owning thread */ }","preventionTips":["Serialize Lock() calls across threads (SemaphoreSlim/monitor)","Keep the lock scope on one thread","Use the queued MicroThreadLock for cross-thread scenarios"],"tags":["csharp","threading","locking","concurrency"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}