stride3d/stride · error · InvalidOperationException
A dispatcher lock must be created from a different thread…
Error message
A dispatcher lock must be created from a different thread that the dispatchers it should lock
What it means
DispatcherLock's DispatcherState acquires an exclusive lock over a dispatcher's message loop. It validates that the constructor is running on a thread OTHER than the dispatcher's own thread (dispatcher.CheckAccess() must be false) — otherwise the code creating the lock would deadlock waiting on tasks that must be pumped by the very thread it is blocking.
Solutions
- Call DispatcherLock.Lock from a background thread, e.g. Task.Run(() => DispatcherLock.Lock(...)) or inside an InvokeAsync on another dispatcher.
- Add ConfigureAwait(false) to awaited calls before Lock so continuations do not resume on the UI thread.
- Restructure the code to dispatch UI work INTO the locked dispatcher rather than taking the lock from the dispatcher thread.
Example fix
// before (running on UI thread)
using (await DispatcherLock.Lock(true, controller, dispatcher)) { ... }
// after
await Task.Run(async () =>
{
using (await DispatcherLock.Lock(true, controller, dispatcher)) { ... }
}); Defensive patterns
Strategy: try-catch
Validate before calling
if (dispatcher.CheckAccess())
throw new InvalidOperationException("Do not take a DispatcherLock from the dispatcher thread; hop to a background thread first."); Try / catch
try { using (await DispatcherLock.Lock(true, controller, dispatcher)) { /* ... */ } }
catch (InvalidOperationException) { await Task.Run(async () => { using (await DispatcherLock.Lock(true, controller, dispatcher)) { /* ... */ } }); } Prevention
- Never take dispatcher locks from UI event handlers directly
- Use ConfigureAwait(false) in library-style code before acquiring locks
- Prefer InvokeAsync into the dispatcher over locking from it
When it happens
Trigger: Calling DispatcherLock.Lock(...) from code already executing on the dispatcher thread being locked (e.g. inside a UI event handler or an async continuation resumed on the UI thread) with an IDispatcherService whose CheckAccess() returns true.
Common situations: Calling Lock from a WPF button click handler; invoking Lock withoutConfigureAwait(false) so an async continuation resumes on the captured UI dispatcher; plugin code that assumes it runs on a background thread.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The current thread was expected to be the dispatcher thread.
- The current thread was expected to be different from the…
- This method must be invoked from the dispatcher thread
- This code must not be executed in the game thread.
- Trying to lock while another thread owns the lock.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/e1d694ee0b87c85c.
Report an issue: GitHub.
Appendix: source
Thrown at sources/editor/Stride.Assets.Presentation/AssetEditors/GameEditor/Services/DispatcherLock.cs:24
using System.Threading.Tasks;
using Stride.Core.Annotations;
using Stride.Core.Extensions;
using Stride.Core.Presentation.Services;
namespace Stride.Assets.Presentation.AssetEditors.GameEditor.Services
{
/// <summary>
/// An object that allows to lock several dispatchers at the same-time to perform an operation in a thread-safe way.
/// </summary>
public class DispatcherLock : IDisposable
{
// TODO: we might want to move that to the plugin level at some point (or even above? in Presentation?)
private struct DispatcherState
{
public DispatcherState([NotNull] IDispatcherService dispatcher)
{
if (dispatcher == null) throw new ArgumentNullException(nameof(dispatcher));
if (dispatcher.CheckAccess()) throw new InvalidOperationException("A dispatcher lock must be created from a different thread that the dispatchers it should lock");
Dispatcher = dispatcher;
Locked = new TaskCompletionSource<int>();
Unlocked = new TaskCompletionSource<int>();
}
public readonly IDispatcherService Dispatcher;
public readonly TaskCompletionSource<int> Locked;
public readonly TaskCompletionSource<int> Unlocked;
}
private readonly List<DispatcherState> dispatcherStates;
/// <summary>
/// Initializes a new instance of the <see cref="DispatcherLock"/> class.
/// </summary>
/// <param name="dispatchers">The dispatchers to lock.</param>
private DispatcherLock([ItemNotNull, NotNull] IEnumerable<IDispatcherService> dispatchers)
{View on GitHub (pinned to 96fad776d2)