AvaloniaUI/Avalonia · error · ObjectDisposedException

CompositionImportedGpuSemaphore

Error message

CompositionImportedGpuSemaphore

What it means

Thrown as ObjectDisposedException (nameof CompositionImportedGpuSemaphore) by the Semaphore property when the imported GPU semaphore is null — after Dispose() nulls it or if Import() failed. As with the GPU image, the IsUsable flag is the intended pre-check (semaphore non-null and matching render context).

Source

Thrown at src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs:148

        _image?.Dispose();
        _image = null!;
    }
}

class CompositionImportedGpuSemaphore : CompositionGpuImportedObjectBase, ICompositionImportedGpuSemaphore
{
    private readonly IPlatformHandle _handle;
    private IPlatformRenderInterfaceImportedSemaphore? _semaphore;

    public CompositionImportedGpuSemaphore(IPlatformHandle handle,
        Compositor compositor, IPlatformRenderInterfaceContext context,
        IExternalObjectsRenderInterfaceContextFeature feature) : base(compositor, context, feature, handle)
    {
        _handle = handle;
    }

    public IPlatformRenderInterfaceImportedSemaphore Semaphore =>
        _semaphore ?? throw new ObjectDisposedException(nameof(CompositionImportedGpuSemaphore));


    public bool IsUsable => _semaphore != null && Compositor.Server.RenderInterface.Value == Context;

    protected override void Import()
    {
        _semaphore = Feature.ImportSemaphore(_handle);
    }

    public override void Dispose()
    {
        _semaphore?.Dispose();
        _semaphore = null;
    }
}

View on GitHub (pinned to 11c5427268)

Solutions

  1. Guard with IsUsable before reading Semaphore; re-import when false.
  2. Drop references after Dispose() and stop submitting the semaphore.
  3. Recreate the imported semaphore when the render interface context changes (catch context-lost during import).

Example fix

// before
var sem = importedGpuSemaphore.Semaphore; // throws post-dispose
// after
if (!importedGpuSemaphore.IsUsable) importedGpuSemaphore = ReimportSemaphore(compositor, handle);
var sem = importedGpuSemaphore.Semaphore;
Defensive patterns

Strategy: validation

Validate before calling

if (!importedGpuSemaphore.IsUsable) importedGpuSemaphore = ReimportSemaphore(compositor, handle);
var sem = importedGpuSemaphore.Semaphore;

Type guard

static bool IsUsable(CompositionImportedGpuSemaphore s) => s.IsUsable;

Try / catch

try { use(importedGpuSemaphore.Semaphore); }
catch (ObjectDisposedException) { importedGpuSemaphore = ReimportSemaphore(compositor, handle); use(importedGpuSemaphore.Semaphore); }

Prevention

When it happens

Trigger: Accessing importedGpuSemaphore.Semaphore after Dispose(), or after a failed ImportSemaphore. Reached when a cross-process/cross-device sync primitive has been torn down but still read.

Common situations: Device loss invalidating imported semaphores; using a semaphore after the composition object that owns it was disposed; a render loop reading a semaphore that a parallel dispose nulled.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/03aa804dd0b9bcbc. Report an issue: GitHub.