{"id":48797083,"url":"https://github.com/soenneker/soenneker.blazor.webworkers","last_synced_at":"2026-06-05T03:00:16.084Z","repository":{"id":348507996,"uuid":"1197863761","full_name":"soenneker/soenneker.blazor.webworkers","owner":"soenneker","description":"Web Worker orchestration in Blazor.","archived":false,"fork":false,"pushed_at":"2026-05-27T16:48:31.000Z","size":489,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-27T18:24:41.568Z","etag":null,"topics":["blazor","blazorlibrary","csharp","dotnet","js","offload","web","webworker","webworkers","webworkersutil","worker","workers"],"latest_commit_sha":null,"homepage":"https://soenneker.com","language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soenneker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"soenneker","thanks_dev":"soenneker"}},"created_at":"2026-04-01T00:30:21.000Z","updated_at":"2026-05-27T16:48:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/soenneker/soenneker.blazor.webworkers","commit_stats":null,"previous_names":["soenneker/soenneker.blazor.webworkers"],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/soenneker/soenneker.blazor.webworkers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.blazor.webworkers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.blazor.webworkers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.blazor.webworkers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.blazor.webworkers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soenneker","download_url":"https://codeload.github.com/soenneker/soenneker.blazor.webworkers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.blazor.webworkers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33927314,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blazor","blazorlibrary","csharp","dotnet","js","offload","web","webworker","webworkers","webworkersutil","worker","workers"],"created_at":"2026-04-14T00:05:31.062Z","updated_at":"2026-06-05T03:00:16.060Z","avatar_url":"https://github.com/soenneker.png","language":"CSS","funding_links":["https://github.com/sponsors/soenneker","https://thanks.dev/soenneker"],"categories":[],"sub_categories":[],"readme":"[![](https://img.shields.io/nuget/v/soenneker.blazor.webworkers.svg?style=for-the-badge)](https://www.nuget.org/packages/soenneker.blazor.webworkers/)\n[![](https://img.shields.io/github/actions/workflow/status/soenneker/soenneker.blazor.webworkers/publish-package.yml?style=for-the-badge)](https://github.com/soenneker/soenneker.blazor.webworkers/actions/workflows/publish-package.yml)\n[![](https://img.shields.io/nuget/dt/soenneker.blazor.webworkers.svg?style=for-the-badge)](https://www.nuget.org/packages/soenneker.blazor.webworkers/)\n[![](https://img.shields.io/badge/Demo-Live-blueviolet?style=for-the-badge\u0026logo=github)](https://soenneker.github.io/soenneker.blazor.webworkers)\n[![](https://img.shields.io/github/actions/workflow/status/soenneker/soenneker.blazor.webworkers/codeql.yml?style=for-the-badge)](https://github.com/soenneker/soenneker.blazor.webworkers/actions/workflows/codeql.yml)\n\n# ![](https://user-images.githubusercontent.com/4441470/224455560-91ed3ee7-f510-4041-a8d2-3fc093025112.png) Soenneker.Blazor.WebWorkers\n### Run background work in Blazor without freezing the UI.\n\nThis package gives you one simple API for browser web workers in Blazor.\n\nUse it when you want to:\n\n- move CPU-heavy work off the UI thread\n- keep the app responsive while work is running\n- run custom JavaScript worker jobs\n- run exported C# methods in JS\n- monitor progress, cancel requests, and inspect worker pool state\n\n## Install\n\n```bash\ndotnet add package Soenneker.Blazor.WebWorkers\n```\n\n## The Basic Idea\n\n`Soenneker.Blazor.WebWorkers` manages worker pools for you.\n\nIn most apps, the flow is:\n\n1. Register `IWebWorkersUtil`\n2. Call `Initialize()`\n3. Create a worker pool\n4. Queue work\n5. Optionally monitor progress, cancel work, or inspect snapshots\n\nThe same `IWebWorkersUtil` service works for both:\n\n- JavaScript workers\n- `.NET` workers\n\n## Quick Start\n\nRegister the service in `Program.cs`:\n\n```csharp\nbuilder.Services.AddWebWorkersUtilAsScoped();\n```\n\nInject it where you want to use it:\n\n```csharp\n@inject IWebWorkersUtil WebWorkers\n```\n\nInitialize it once before first use:\n\n```csharp\nawait WebWorkers.Initialize();\n```\n\n## Common Workflow\n\n### 1. Create a JavaScript worker pool\n\nPoint the pool at your worker script:\n\n```csharp\nawait WebWorkers.CreatePool(new WebWorkerPoolOptions\n{\n    WorkerCount = 4,\n    ScriptPath = \"js/workers/app.worker.js\"\n});\n```\n\nThat creates the default JavaScript pool.\n\n### 2. Queue a JavaScript job\n\nPass a workload name and payload:\n\n```csharp\nusing System.Text.Json;\nusing Soenneker.Blazor.WebWorkers.Dtos;\n\nWebWorkerResult\u003cJsonElement\u003e result = await WebWorkers.Run\u003cJsonElement\u003e(\n    \"prime-analysis\",\n    new\n    {\n        upperBound = 180000\n    },\n    progress =\u003e\n    {\n        Console.WriteLine($\"{progress.Percent:0}% - {progress.Message}\");\n        return ValueTask.CompletedTask;\n    });\n```\n\nYour worker script is responsible for understanding the workload name and payload.\n\n### 3. Cancel or inspect work\n\n```csharp\nawait WebWorkers.CancelRequest(\"default\", jobId);\n\nWebWorkerCoordinatorSnapshot snapshot = await WebWorkers.GetCoordinatorSnapshot();\n```\n\n## JavaScript Worker Path\n\nThis is the best fit when your worker logic already lives in JavaScript, or when you want full control over the worker script.\n\nImportant points:\n\n- JavaScript pools use `WebWorkerBackend.JavaScript`\n- the default pool name is `\"default\"`\n- you usually only need `WorkerCount` and `ScriptPath`\n- jobs are queued with a `workloadName` and optional `payload`\n- you can report progress back while work is running\n\nYou can also target a specific named pool:\n\n```csharp\nawait WebWorkers.CreatePool(new WebWorkerPoolOptions\n{\n    Name = \"images\",\n    WorkerCount = 2,\n    ScriptPath = \"js/workers/image.worker.js\"\n});\n\nWebWorkerResult\u003cJsonElement\u003e result = await WebWorkers.Run\u003cJsonElement\u003e(\n    \"images\",\n    \"generate-thumbnail\",\n    new\n    {\n        width = 300,\n        height = 300\n    });\n```\n\n### Worker scripts from a Razor class library\n\nIf a worker file ships from an RCL, build the static asset path like this:\n\n```csharp\nstring workerPath = WebWorkerAssetPaths.WorkerFromPackage(\n    \"Soenneker.Blazor.Opfs\",\n    \"opfs.worker.js\");\n```\n\nThen use that path as the pool's `ScriptPath`.\n\n## .NET Worker Path\n\nThis package can also run exported C# methods inside a browser worker by booting a second .NET WebAssembly runtime in that worker.\n\nThis path is useful when:\n\n- your work is already written in C#\n- you want to keep background logic in your Blazor app\n- you do not want to hand-write a JavaScript worker for that job\n\n### Requirements\n\nThe `.NET` worker path requires:\n\n- Blazor WebAssembly\n- `AllowUnsafeBlocks=true` in the app `.csproj`\n- exported worker methods defined in the main app assembly\n- worker methods marked with `[JSExport]`\n\nExample `.csproj` setting:\n\n```xml\n\u003cPropertyGroup\u003e\n  \u003cAllowUnsafeBlocks\u003etrue\u003c/AllowUnsafeBlocks\u003e\n\u003c/PropertyGroup\u003e\n```\n\n### Define an exported worker method\n\n```csharp\nusing System.Runtime.InteropServices.JavaScript;\nusing System.Runtime.Versioning;\nusing Soenneker.Utils.Json;\n\n[SupportedOSPlatform(\"browser\")]\npublic static partial class WorkerExports\n{\n    [JSExport]\n    public static string? AnalyzePrimeRange(int upperBound)\n    {\n        return JsonUtil.Serialize(new\n        {\n            upperBound,\n            message = \"Ran inside a .NET worker.\"\n        });\n    }\n}\n```\n\n### Create a .NET worker pool\n\n```csharp\nusing Soenneker.Blazor.WebWorkers.Enums;\nusing Soenneker.Blazor.WebWorkers.Options;\n\nawait WebWorkers.CreatePool(new WebWorkerPoolOptions\n{\n    Backend = WebWorkerBackend.DotNet,\n    WorkerCount = 1\n});\n```\n\n### Invoke the exported method\n\nYou can call it with a request object:\n\n```csharp\nusing Soenneker.Blazor.WebWorkers.Dtos;\nusing Soenneker.Blazor.WebWorkers.Enums;\n\nWebWorkerResult\u003cstring?\u003e result = await WebWorkers.Run\u003cstring?\u003e(new WebWorkerRequest\n{\n    Backend = WebWorkerBackend.DotNet,\n    MethodName = \"MyApp.WorkerExports.AnalyzePrimeRange\",\n    Arguments = [220000]\n});\n```\n\nOr use the expression-based overload:\n\n```csharp\nWebWorkerResult\u003cMyResult\u003e result =\n    await WebWorkers.Run(() =\u003e WorkerExports.RunAnalysisAsync(220000));\n```\n\nThe expression-based overload is often the easiest option because it avoids building the request manually.\n\n## Important Types\n\n### `IWebWorkersUtil`\n\nThe main service you work with. It handles:\n\n- initialization\n- pool creation and destruction\n- job execution\n- cancellation\n- pool and coordinator snapshots\n\n### `WebWorkerPoolOptions`\n\nUsed when creating a pool.\n\nThe most important properties are:\n\n- `Backend`\n- `Name`\n- `ScriptPath`\n- `WorkerCount`\n- `WorkerType`\n- `RuntimeScriptPath`\n- `BootConfigPath`\n- `RestartFaultedWorkers`\n\n### `WebWorkerRequest`\n\nUsed when you need full control over a queued request.\n\nThe most important properties are:\n\n- `PoolName`\n- `Backend`\n- `RequestId`\n- `WorkloadName`\n- `MethodName`\n- `Payload`\n- `Arguments`\n- `TimeoutMs`\n\n## Monitoring and Cancellation\n\nYou can:\n\n- receive progress callbacks while a job is running\n- cancel a queued or running request\n- inspect one pool or all pools\n- inspect a full coordinator snapshot\n\nExamples:\n\n```csharp\nawait WebWorkers.CancelRequest(\"default\", requestId);\n\nWebWorkerPoolSnapshot? pool = await WebWorkers.GetPoolSnapshot(\"default\");\nIReadOnlyList\u003cWebWorkerPoolSnapshot\u003e pools = await WebWorkers.GetPoolSnapshots();\nWebWorkerCoordinatorSnapshot snapshot = await WebWorkers.GetCoordinatorSnapshot();\n```\n\n## Things To Know\n\n- JavaScript and `.NET` workers share the same top-level service: `IWebWorkersUtil`\n- JavaScript jobs use `WorkloadName` plus `Payload`\n- `.NET` jobs use `MethodName` plus `Arguments`\n- the `.NET` worker path is for Blazor WebAssembly\n- `.NET` worker methods should usually return simple values or serialized JSON\n- if your worker code naturally returns `ValueTask`, expose a small `Task`-returning `[JSExport]` wrapper\n- cancellation of a running `.NET` worker request may require terminating and replacing the backing worker\n\n## Which Path Should I Use?\n\nUse the JavaScript path when:\n\n- you already have worker logic in JavaScript\n- you need a custom browser worker script\n- your workload is naturally message-based\n\nUse the `.NET` path when:\n\n- your workload is already implemented in C#\n- you want to stay in C# as much as possible\n- you are building a Blazor WebAssembly app (Not available in Server)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoenneker%2Fsoenneker.blazor.webworkers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoenneker%2Fsoenneker.blazor.webworkers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoenneker%2Fsoenneker.blazor.webworkers/lists"}