WebLocks
Cooperative, cross-tab resource locking through navigator.locks: acquire a named lock as an IAsyncDisposable handle, run a callback while holding it, and query which locks are held or pending across the whole origin.
@inject Bit.Butil.WebLocks webLocksMDN reference
Returns true when the runtime exposes navigator.locks.
var supported = await webLocks.IsSupported();Acquire hands you an IAsyncDisposable handle; dispose it to release the lock - the natural .NET counterpart to the callback-scoped JS API. By default the call waits until the lock is granted. With ifAvailable set to true it returns null immediately when the lock is taken, and steal transfers ownership from the current holder (use with care). Shared mode allows multiple concurrent holders, exclusive (the default) allows one.
await using var handle = await webLocks.Acquire("my-resource");
// ... the lock is held here ...
// released when the handle is disposed
// non-blocking attempt:
var maybe = await webLocks.Acquire("my-resource", ifAvailable: true);
if (maybe is null)
{
// somebody else holds it
}Run acquires the lock, executes your callback and releases automatically - even when the callback throws. This demo holds the lock for three seconds; race it from a second tab to watch the other request queue up.
await webLocks.Run("my-resource", async () =>
{
// the lock is held for the duration of this callback
await Task.Delay(3000);
});
// released automaticallyReturns a snapshot of the origin's lock manager: every currently held lock and every pending request, each with its name, mode and an opaque client id identifying the tab or worker. Useful for diagnostics - acquire a lock in another tab first, then query here.
var snapshot = await webLocks.Query();
foreach (var held in snapshot.Held)
{
// held.Name, held.Mode ("exclusive" / "shared"), held.ClientId
}
foreach (var pending in snapshot.Pending)
{
// requests still waiting for the lock
}API reference
ValueTask<bool> IsSupported()ValueTask<IAsyncDisposable?> Acquire(string name, WebLockMode mode = WebLockMode.Exclusive, bool ifAvailable = false, bool steal = false, CancellationToken cancellationToken = default)ValueTask Run(string name, Func<ValueTask> action, WebLockMode mode = WebLockMode.Exclusive, bool ifAvailable = false, bool steal = false, CancellationToken cancellationToken = default)ValueTask<WebLockSnapshot> Query()enum WebLockMode { Exclusive, Shared }WebLockInfo[] Held { get; set; }WebLockInfo[] Pending { get; set; }string Name { get; set; }string Mode { get; set; }string ClientId { get; set; }