Skip to content

Error when using multiple threads #912

@viruseg

Description

@viruseg

ComputeSharp 3.0.2
.net 9.0.203

If you run the shader execution from two threads at the same time, then one of the threads will hang forever without any errors.

If you run three threads, the process will fail:

Unhandled exception. System.ArgumentException: An item with the same key has already been added.
   at System.Runtime.CompilerServices.ConditionalWeakTable`2.Add(TKey key, TValue value)
   at ComputeSharp.Shaders.Loading.PipelineDataLoader`1.CreatePipelineData(GraphicsDevice device) in /_/src/ComputeSharp/Shaders/Loading/PipelineDataLoader{T}.cs:line 53
   at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValueLocked(TKey key, CreateValueCallback createValueCallback)
   at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValue(TKey key, CreateValueCallback createValueCallback)
   at ComputeSharp.Shaders.Loading.PipelineDataLoader`1.GetPipelineData(GraphicsDevice device) in /_/src/ComputeSharp/Shaders/Loading/PipelineDataLoader{T}.cs:line 31
   at ComputeSharp.ComputeContext.Run[T](Int32 x, Int32 y, Int32 z, T& shader) in /_/src/ComputeSharp/Shaders/ComputeContext.cs:line 166
   at ComputeSharp.ComputeContext.Run[T](Int32 x, Int32 y, T& shader) in /_/src/ComputeSharp/Shaders/ComputeContext.cs:line 139
   at ComputeSharp.ComputeContextExtensions.For[T](ComputeContext& context, Int32 x, Int32 y, T& shader) in /_/src/ComputeSharp/Shaders/Extensions/ComputeContextExtensions.cs:line 503
   at Program.<<Main>$>g__Execute|0_3(Byte[] sourceBytes, Rectangle cropRect, ImageFormat targetImageFormat, GraphicsDevice graphicsDevice) in C:\ConsoleApp\Program.cs:line 40
   at Program.<>c__DisplayClass0_0.<<Main>$>b__0() in C:\ConsoleApp\Program.cs:line 12
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
   at Program.<Main>$(String[] args) in C:\ConsoleApp\Program.cs:line 28
   at Program.<Main>(String[] args)

Here is a sample code to reproduce the error:

using System.Drawing;
using ComputeSharp;

#pragma warning disable CA1416

var graphicsDevice = GraphicsDevice.GetDefault();
var fileBytes = File.ReadAllBytes("0.png");
var rectangle = new Rectangle(100, 100, 100, 100);

var task0 = Task.Run(() =>
{
    var bytes = Execute(fileBytes, rectangle, ImageFormat.Bmp, graphicsDevice);
    Console.WriteLine($"0 {bytes.Length}");
});

var task1 = Task.Run(() =>
{
    var bytes = Execute(fileBytes, rectangle, ImageFormat.Bmp, graphicsDevice);
    Console.WriteLine($"1 {bytes.Length}");
});

var task2 = Task.Run(() =>
{
    var bytes = Execute(fileBytes, rectangle, ImageFormat.Bmp, graphicsDevice);
    Console.WriteLine($"1 {bytes.Length}");
});

await Task.WhenAll(task0, task1, task2);

Console.WriteLine("Complete");
return;

byte[] Execute(byte[] sourceBytes, Rectangle cropRect, ImageFormat targetImageFormat,  GraphicsDevice graphicsDevice)
{
    var sourceTex = graphicsDevice.LoadReadOnlyTexture2D<Bgra32, float4>(sourceBytes);
    var targetTex = graphicsDevice.AllocateReadWriteTexture2D<Bgra32, float4>(cropRect.Width, cropRect.Height);

    using (var context = graphicsDevice.CreateComputeContext())
    {
        context.For(targetTex.Width,
                    targetTex.Height,
                    new InternalCropShader(sourceTex,
                                           targetTex,
                                           cropRect.X,
                                           cropRect.Y));
    }

    var ms = new MemoryStream();
    targetTex.Save(ms, targetImageFormat);
    return ms.ToArray();
}

[ThreadGroupSize(DefaultThreadGroupSizes.XY)]
[GeneratedComputeShaderDescriptor]
internal readonly partial struct InternalCropShader
(
    IReadOnlyNormalizedTexture2D<float4> sourceTex,
    IReadWriteNormalizedTexture2D<float4> targetTex,
    int offsetX,
    int offsetY
) : IComputeShader
{
    public void Execute()
    {
        var sourceXY = ThreadIds.XY + new int2(offsetX, offsetY);
        targetTex[ThreadIds.XY] = sourceTex[sourceXY];
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🐛Something isn't workinguntriaged 🧰A new issue that needs initial triage

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions