Soenneker.Blazor.WebWorkers 11.0.228

Prefix Reserved
dotnet add package Soenneker.Blazor.WebWorkers --version 11.0.228
                    
NuGet\Install-Package Soenneker.Blazor.WebWorkers -Version 11.0.228
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Soenneker.Blazor.WebWorkers" Version="11.0.228" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Soenneker.Blazor.WebWorkers" Version="11.0.228" />
                    
Directory.Packages.props
<PackageReference Include="Soenneker.Blazor.WebWorkers" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Soenneker.Blazor.WebWorkers --version 11.0.228
                    
#r "nuget: Soenneker.Blazor.WebWorkers, 11.0.228"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Soenneker.Blazor.WebWorkers@11.0.228
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Soenneker.Blazor.WebWorkers&version=11.0.228
                    
Install as a Cake Addin
#tool nuget:?package=Soenneker.Blazor.WebWorkers&version=11.0.228
                    
Install as a Cake Tool

alternate text is missing from this package README image alternate text is missing from this package README image alternate text is missing from this package README image alternate text is missing from this package README image alternate text is missing from this package README image

Soenneker.Blazor.WebWorkers

Runs CPU-heavy JavaScript workloads or exported C# methods in managed browser-worker pools without blocking Blazor's UI thread.

Install

dotnet add package Soenneker.Blazor.WebWorkers

Register and inject the scoped utility:

using Soenneker.Blazor.WebWorkers.Registrars;

builder.Services.AddWebWorkersUtilAsScoped();
@using Soenneker.Blazor.WebWorkers.Abstract
@inject IWebWorkersUtil WebWorkers

Operations initialize the interop on demand. Initialize() is optional and only preloads it.

JavaScript workers

Create a pool for a worker script served by your application:

using Soenneker.Blazor.WebWorkers.Options;

await WebWorkers.CreatePool(new WebWorkerPoolOptions
{
    WorkerCount = 4,
    ScriptPath = "js/workers/app.worker.js"
});

This creates the pool named default. Queue a workload and structured-clone-compatible payload:

using System.Text.Json;
using Soenneker.Blazor.WebWorkers.Dtos;

WebWorkerResult<JsonElement> result = await WebWorkers.Run<JsonElement>(
    "prime-analysis",
    new { upperBound = 180_000 },
    progress =>
    {
        Console.WriteLine($"{progress.Percent:0}% — {progress.Message}");
        return ValueTask.CompletedTask;
    },
    cancellationToken);

Worker failures and cooperative cancellations are returned in WebWorkerResult<T>; inspect State, Result, and ErrorMessage. Cancelling the .NET caller can throw OperationCanceledException while it waits.

Worker protocol

Your worker receives run and cancel messages. It must send a terminal completed, cancelled, or faulted message with the same jobId:

let activeJobId = null;
let cancellationRequested = false;
const yieldToWorker = () => new Promise(resolve => setTimeout(resolve, 0));

self.onmessage = async event => {
    const message = event.data;

    if (message?.type === "cancel" && message.jobId === activeJobId) {
        cancellationRequested = true;
        return;
    }

    if (message?.type !== "run") return;

    activeJobId = message.jobId;
    cancellationRequested = false;

    try {
        let total = 0;

        for (let index = 0; index < message.payload.count; index++) {
            if (cancellationRequested) throw new Error("cancelled");
            total += index;

            if (index % 1000 === 0) {
                self.postMessage({
                    type: "progress",
                    jobId: message.jobId,
                    percent: (index / message.payload.count) * 100,
                    completedUnits: index,
                    totalUnits: message.payload.count
                });

                await yieldToWorker();
            }
        }

        self.postMessage({ type: "completed", jobId: message.jobId, result: total });
    } catch (error) {
        self.postMessage({
            type: cancellationRequested ? "cancelled" : "faulted",
            jobId: message.jobId,
            errorMessage: error instanceof Error ? error.message : "Worker failed."
        });
    } finally {
        activeJobId = null;
        cancellationRequested = false;
    }
};

self.postMessage({ type: "ready" });

A cancel message cannot be observed during one uninterrupted synchronous loop. Split CPU work into chunks and yield to the worker event loop. TimeoutMs is enforced by terminating the worker; a timed-out result has Faulted state.

Use WorkerType = WebWorkerScriptType.Module when the script imports ES modules. For a worker shipped by a Razor class library:

string scriptPath = WebWorkerAssetPaths.WorkerFromPackage(
    "My.Worker.Package",
    "image.worker.js");

Named pools and cancellation

Set WebWorkerPoolOptions.Name to isolate scripts or concurrency limits. Assign a request ID when another handler needs to cancel the job:

var request = new WebWorkerRequest
{
    PoolName = "images",
    RequestId = $"thumbnail-{imageId}",
    WorkloadName = "generate-thumbnail",
    Payload = new { imageId },
    TimeoutMs = 15_000
};

Task<WebWorkerResult<JsonElement>> running =
    WebWorkers.Run<JsonElement>(request).AsTask();

await WebWorkers.CancelRequest("images", request.RequestId);
WebWorkerResult<JsonElement> result = await running;

JavaScript cancellation is cooperative unless a timeout terminates the worker. A running .NET request is cancelled by terminating and replacing its worker.

.NET workers

The .NET backend starts another WebAssembly runtime inside each browser worker and invokes [JSExport] methods from the main application assembly. It is for Blazor WebAssembly and costs more startup time and memory than a JavaScript worker.

Enable unsafe blocks in the application project:

<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Define a static exported method with JavaScript-interoperable arguments and results:

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
using System.Text.Json;

[SupportedOSPlatform("browser")]
public static partial class WorkerExports
{
    [JSExport]
    public static async Task<string> AnalyzePrimeRange(int upperBound)
    {
        await Task.Yield();
        return JsonSerializer.Serialize(new { upperBound });
    }
}

The expression overload extracts the static method and argument values; it does not run the method on the UI thread:

WebWorkerResult<string> result =
    await WebWorkers.Run(() => WorkerExports.AnalyzePrimeRange(220_000));

With no .NET pool name, the default one-worker pool is created automatically. Create it explicitly to configure concurrency:

await WebWorkers.CreatePool(new WebWorkerPoolOptions
{
    Backend = WebWorkerBackend.DotNet,
    WorkerCount = 2
});

The expression must be a direct call to a static Task-returning method. A manual request must select the .NET backend, use the fully qualified exported MethodName, and provide Arguments in declaration order.

Inspect and destroy pools

Snapshots are point-in-time diagnostics:

WebWorkerPoolSnapshot? pool = await WebWorkers.GetPoolSnapshot("default");
IReadOnlyList<WebWorkerPoolSnapshot> pools = await WebWorkers.GetPoolSnapshots();
WebWorkerCoordinatorSnapshot snapshot = await WebWorkers.GetCoordinatorSnapshot();

await WebWorkers.DestroyPool("default");

Destroying or replacing a pool terminates its workers and returns Cancelled results for attached requests. Disposing the scoped utility cleans up every pool.

Security and browser constraints

  • Worker scripts execute with your application's origin privileges. Use trusted, application-controlled script paths; never accept one from user input.
  • Payloads use the structured-clone algorithm. Functions, DOM nodes, and many runtime-specific objects cannot be sent by this API.
  • Transfer lists, SharedArrayBuffer, and direct DOM access are not exposed.
  • Worker count multiplies memory use, especially for the .NET backend. Start small and measure on supported devices.
  • Browser workers are unavailable during server prerendering and in Blazor Server. Start pools only after WebAssembly is running.
Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
11.0.228 0 11/15/2026
11.0.227 0 11/14/2026
11.0.226 50 11/14/2026
11.0.225 76 11/12/2026
11.0.224 70 11/11/2026
11.0.223 68 11/11/2026
11.0.221 70 11/11/2026
11.0.219 70 11/11/2026
11.0.218 71 11/11/2026
11.0.217 68 11/11/2026
11.0.216 77 11/11/2026
11.0.215 77 11/11/2026
11.0.214 77 11/11/2026
11.0.213 97 11/4/2026
11.0.212 98 11/3/2026
11.0.211 88 11/3/2026
11.0.210 90 11/3/2026
11.0.209 88 11/3/2026
11.0.208 88 11/3/2026
11.0.206 94 11/3/2026
Loading failed

Update dependency Soenneker.Gen.Web.Minifier to 11.0.69 (#881)