{"id":15092956,"url":"https://github.com/kristofferstrube/blazor.webworkers","last_synced_at":"2025-04-12T06:31:01.702Z","repository":{"id":245022759,"uuid":"817030754","full_name":"KristofferStrube/Blazor.WebWorkers","owner":"KristofferStrube","description":"A Blazor wrapper for the Web Workers part of the HTML API.","archived":false,"fork":false,"pushed_at":"2024-10-02T20:56:07.000Z","size":7907,"stargazers_count":16,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-03T21:04:47.942Z","etag":null,"topics":["blazor","multithreading","wasm","webworkers","worker"],"latest_commit_sha":null,"homepage":"","language":"C#","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/KristofferStrube.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-18T22:02:49.000Z","updated_at":"2025-03-29T15:19:19.000Z","dependencies_parsed_at":"2024-11-07T11:02:19.932Z","dependency_job_id":"f321501a-da31-4a02-8c4e-94c92db9b491","html_url":"https://github.com/KristofferStrube/Blazor.WebWorkers","commit_stats":{"total_commits":28,"total_committers":1,"mean_commits":28.0,"dds":0.0,"last_synced_commit":"05d5baab88123f4038054ddcd53f25375fa7ee3a"},"previous_names":["kristofferstrube/blazor.webworkers"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KristofferStrube%2FBlazor.WebWorkers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KristofferStrube%2FBlazor.WebWorkers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KristofferStrube%2FBlazor.WebWorkers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KristofferStrube%2FBlazor.WebWorkers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KristofferStrube","download_url":"https://codeload.github.com/KristofferStrube/Blazor.WebWorkers/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248529383,"owners_count":21119499,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","multithreading","wasm","webworkers","worker"],"created_at":"2024-09-25T11:02:02.275Z","updated_at":"2025-04-12T06:31:01.649Z","avatar_url":"https://github.com/KristofferStrube.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](/LICENSE)\n[![GitHub issues](https://img.shields.io/github/issues/KristofferStrube/Blazor.WebWorkers)](https://github.com/KristofferStrube/Blazor.WebWorkers/issues)\n[![GitHub forks](https://img.shields.io/github/forks/KristofferStrube/Blazor.WebWorkers)](https://github.com/KristofferStrube/Blazor.WebWorkers/network/members)\n[![GitHub stars](https://img.shields.io/github/stars/KristofferStrube/Blazor.WebWorkers)](https://github.com/KristofferStrube/Blazor.WebWorkers/stargazers)\n\u003c!--[![NuGet Downloads (official NuGet)](https://img.shields.io/nuget/dt/KristofferStrube.Blazor.WebWorkers?label=NuGet%20Downloads)](https://www.nuget.org/packages/KristofferStrube.Blazor.WebWorkers/)--\u003e\n\n# Blazor.WebWorkers\nA Blazor wrapper for the [Web Workers part of the HTML API.](https://html.spec.whatwg.org/multipage/workers.html)\nThe API defines ways to run scripts in the background independently of the primary thread. This allows for long-running scripts that are not interrupted by scripts that respond to clicks or other user interactions and allows long tasks to be executed without yielding to keep the page responsive. This project implements a wrapper around the API for Blazor so that we can easily and safely create workers.\n\n**This wrapper is still just an experiment.**\n\n# Demo\nThe sample project can be demoed at https://kristofferstrube.github.io/Blazor.WebWorkers/\n\nOn each page, you can find the corresponding code for the example in the top right corner.\n\n# Getting Started\n\nMany others like [Tewr/BlazorWorker](https://github.com/Tewr/BlazorWorker) and [LostBeard/SpawnDev.BlazorJS](https://github.com/LostBeard/SpawnDev.BlazorJS) have made libraries like this before. This project differs a bit from the other projects by utilizing [the wasm-experimental workload](https://devblogs.microsoft.com/dotnet/use-net-7-from-any-javascript-app-in-net-7/). This simplifies the code needed for this to work a lot. The catch to this is that you will need to have the code for your workers in another project. For me this is not only a negative as it also makes it very clear that they do not share memory and that they run in separate contexts, similar to how the *Blazor WASM* project is separate in a *Blazor WebApp*.\n\nSo to get started you really only need to *create a new console project* and then make a few adjustments to the `.csproj`. In the end it should look something like this:\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\"\u003e\n\n  \u003cPropertyGroup\u003e\n    \u003cTargetFramework\u003enet8.0\u003c/TargetFramework\u003e\n    \u003cOutputType\u003eExe\u003c/OutputType\u003e\n    \u003cRuntimeIdentifier\u003ebrowser-wasm\u003c/RuntimeIdentifier\u003e\n    \u003cNullable\u003eenable\u003c/Nullable\u003e\n    \u003cImplicitUsings\u003eenable\u003c/ImplicitUsings\u003e\n    \u003cAllowUnsafeBlocks\u003etrue\u003c/AllowUnsafeBlocks\u003e\n  \u003c/PropertyGroup\u003e\n\n  \u003cItemGroup\u003e\n    \u003cProjectReference Include=\"\u003cpath-to-blazor-webworkers-until-i-release-a-nuget-package\u003e\\KristofferStrube.Blazor.WebWorkers.csproj\" /\u003e\n  \u003c/ItemGroup\u003e\n\n\u003c/Project\u003e\n```\nAnd then you can do whatever you want in the `Program.cs` file, but I've added some helpers that make it easier to communicate with the main window and create objects.\n\n## SlimWorker\n\nHere I have an example of the code needed for a simple pong worker that broadcasts when it is ready to listen for a ping, responds with a pong when it receives that, and then shuts down.\n\n```csharp\nusing KristofferStrube.Blazor.WebWorkers;\n\nif (!OperatingSystem.IsBrowser())\n    throw new PlatformNotSupportedException(\"Can only be run in the browser!\");\n\nConsole.WriteLine(\"Hey this is running on another thread!\");\n\nbool keepRunning = true;\n\nif (args.Length \u003e= 1)\n{\n    Console.WriteLine($\"The worker was initialized with arguments: [{string.Join(\", \", args)}]\");\n}\n\n// This is a helper for listening on messages.\nImports.RegisterOnMessage(e =\u003e\n{\n    // If we receive a \"ping\", respond with \"pong\".\n    if (e.GetTypeOfProperty(\"data\") == \"string\" \u0026\u0026 e.GetPropertyAsString(\"data\") == \"ping\")\n    {\n        // Helper for posting a message.\n        Console.WriteLine(\"Received ping; Sending pong!\");\n        Imports.PostMessage(\"pong\");\n        keepRunning = false;\n    }\n});\n\nConsole.WriteLine(\"We are now listening for messages.\");\nImports.PostMessage(\"ready\");\n\n// We run forever to keep it alive.\nwhile (keepRunning)\n    await Task.Delay(100);\n\nConsole.WriteLine(\"Worker done, so stopping!\");\n```\n\nAnd with very little extra setup we can start this and post a message to it once it is ready. For this we use a very simple abstraction over the worker that enable us to specify an assembly name and some arguments for the worker.\n\n```csharp\nSlimWorker slimWorker = await SlimWorker.CreateAsync(\n    jSRuntime: JSRuntime,\n    assembly: typeof(AssemblyPongWorker).Assembly.GetName().Name!,\n    [\"Argument1\", \"Argument2\"]\n);\n\nEventListener\u003cMessageEvent\u003e eventListener = default!;\neventListener = await EventListener\u003cMessageEvent\u003e.CreateAsync(JSRuntime, async e =\u003e\n{\n    object? data = await e.Data.GetValueAsync();\n    switch (data)\n    {\n        case \"ready\":\n            Log(\"We are sending a ping!\");\n            await slimWorker.PostMessageAsync(\"ping\");\n            break;\n        case \"pong\":\n            Log(\"We received a pong!\");\n            await slimWorker.RemoveOnMessageEventListenerAsync(eventListener);\n            await eventListener.DisposeAsync();\n            await slimWorker.DisposeAsync();\n            break;\n    }\n});\nawait slimWorker.AddOnMessageEventListenerAsync(eventListener);\n```\nThis looks like so:\n\n![ping pong demo](./docs/ping-pong.gif?raw=true)\n\n## JobWorker\n\nAnother more basic abstraction is the `JobWorker`. This simple abstraction runs some job with an input and an output on a worker. The `.csproj` look identical to the one used for the `SlimWorker`.\n\nBut what differs is that we need to create a class that implements the interface `IJob\u003cTInput, TOutput\u003e` in the worker project. A simple way to do this is by extending the abstract class `JsonJob` which uses JSON as the format for transfering its input and output. This limits us to only use inputs and outputs that can be JSON serialized and deserialized.\n\nHere were implement a job that can find the sum of the codes of each individual char in a string.\n\n```csharp\npublic class StringSumJob : JsonJob\u003cstring, int\u003e\n{\n    public override int Work(string input)\n    {\n        int result = 0;\n        for (int i = 0; i \u003c input.Length; i++)\n        {\n            result += input[i];\n        }\n        return result;\n    }\n}\n```\n\nThen we need to replace the content of the `Program.cs` in the worker project with the following to instantiate the job.\n\n```csharp\nif (!OperatingSystem.IsBrowser())\n    throw new PlatformNotSupportedException(\"Can only be run in the browser!\");\n\nnew StringSumJob().Execute(args);\n```\n\nFinally to call the worker from our main Blazor program we only need the following.\n\n```csharp\nvar jobWorker = await JobWorker\u003cstring, int, StringSumJob\u003e.CreateAsync(JSRuntime);\n\nint result = await jobWorker.ExecuteAsync(input);\n```\n\nWe can create the `JobWorker` a single time and then run it multiple times with different inputs. Doing this spares us from importing the needed WASM assemblies multiple times which can make consecutive runs much faster.\n\n\n# Related repositories\nThe library uses the following other packages to support its features:\n- https://github.com/KristofferStrube/Blazor.WebIDL (To make error handling JSInterop)\n- https://github.com/KristofferStrube/Blazor.DOM (To implement *EventTarget*'s in the package like `Worker`)\n- https://github.com/KristofferStrube/Blazor.Window (To use the `MessageEvent` type)\n\n# Related articles\nThis repository was built with inspiration and help from the following series of articles:\n\n- [Multithreading in Blazor WASM using Web Workers](https://kristoffer-strube.dk/post/multithreading-in-blazor-wasm-using-web-workers/)\n- [Use .NET from any JavaScript app in .NET 7](https://devblogs.microsoft.com/dotnet/use-net-7-from-any-javascript-app-in-net-7/)\n- [Typed exceptions for JSInterop in Blazor](https://kristoffer-strube.dk/post/typed-exceptions-for-jsinterop-in-blazor/)\n- [Blazor WASM 404 error and fix for GitHub Pages](https://blog.elmah.io/blazor-wasm-404-error-and-fix-for-github-pages/)\n- [How to fix Blazor WASM base path problems](https://blog.elmah.io/how-to-fix-blazor-wasm-base-path-problems/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristofferstrube%2Fblazor.webworkers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkristofferstrube%2Fblazor.webworkers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristofferstrube%2Fblazor.webworkers/lists"}