Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>${DefaultNetCoreTargetFramework}</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<GeneratedContent Include="BlazorWebCSharp.csproj.in" OutputPath="content/BlazorWeb-CSharp/BlazorWebCSharp.1/BlazorWebCSharp.1.csproj" />
<GeneratedContent Include="BlazorWebCSharp.Client.csproj.in" OutputPath="content/BlazorWeb-CSharp/BlazorWebCSharp.1.Client/BlazorWebCSharp.1.Client.csproj" />
<GeneratedContent Include="ComponentsWebAssembly-CSharp.csproj.in" OutputPath="content/ComponentsWebAssembly-CSharp/ComponentsWebAssembly-CSharp.csproj" />
<GeneratedContent Include="WebWorker-CSharp.csproj.in" OutputPath="content/WebWorker-CSharp/Company.WebWorker1.csproj" />
<GeneratedContent Include="BlazorWebWorker-CSharp.csproj.in" OutputPath="content/BlazorWebWorker-CSharp/Company.WebWorker1.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "Microsoft",
"name": ".NET Web Worker",
"description": "A project for creating a class library that runs .NET code in a Web Worker.",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net11.0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
"postActions/restore/manualInstructions/default/text": "Run 'dotnet restore'"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
"author": "Microsoft",
"classifications": [
"Web",
"Blazor",
"WebAssembly",
"Worker",
"Library"
],
"name": ".NET Web Worker",
"name": "Blazor Web Worker",
"generatorVersions": "[1.0.0.0-*)",
"description": "A project for creating a class library that runs .NET code in a Web Worker.",
"groupIdentity": "Microsoft.Web.Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker.",
Comment thread
ilonatommy marked this conversation as resolved.
Outdated
"groupIdentity": "Microsoft.Web.Blazor.WebWorker",
"precedence": "10000",
"identity": "Microsoft.Web.Worker.Library.CSharp.10.0",
"shortName": "webworker",
"identity": "Microsoft.Web.Blazor.WebWorker.Library.CSharp.11.0",
"shortName": "blazorwebworker",
"tags": {
"language": "C#",
"type": "project"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "Microsoft",
"name": ".NET Web Worker",
"description": "A project for creating a class library that runs .NET code in a WebWorker, keeping your WebAssembly UI responsive during heavy computations.",
"name": "Blazor Web Worker",
"description": "A project for creating a Blazor class library that runs .NET code in a Web Worker, keeping your WebAssembly UI responsive during heavy computations.",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/0/description": "Target net11.0",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Microsoft.JSInterop;

namespace Company.WebWorker1;

public sealed class WebWorkerClient(IJSObjectReference worker) : IAsyncDisposable
{
private const int DefaultTimeoutMs = 60000;
private static readonly string DefaultAssemblyName = typeof(WebWorkerClient).Assembly.GetName().Name!;

public static async Task<WebWorkerClient> CreateAsync(IJSRuntime jsRuntime, int timeoutMs = DefaultTimeoutMs, string? assemblyName = null, CancellationToken cancellationToken = default)
{
await using var module = await jsRuntime.InvokeAsync<IJSObjectReference>(
"import", cancellationToken, "./_content/Company.WebWorker1/dotnet-web-worker-client.js");

var resolvedName = assemblyName ?? DefaultAssemblyName;
var options = new { assemblyName = resolvedName };
var workerRef = await module.InvokeAsync<IJSObjectReference>("create", cancellationToken, timeoutMs, options);

return new WebWorkerClient(workerRef);
}

// Invokes a [JSExport] method from the web worker.
// The method string is the fully qualified path: "AssemblyName.ClassName.MethodName".
// Arguments and return values must be primitive types or strings.
public async Task<TResult> InvokeAsync<TResult>(string method, object[] args, int timeoutMs = DefaultTimeoutMs, CancellationToken cancellationToken = default)
{
return await worker.InvokeAsync<TResult>("invoke", cancellationToken, [method, args, timeoutMs]);
}

public async Task InvokeVoidAsync(string method, object[] args, int timeoutMs = DefaultTimeoutMs, CancellationToken cancellationToken = default)
{
await worker.InvokeVoidAsync("invoke", cancellationToken, [method, args, timeoutMs]);
}

public async ValueTask DisposeAsync()
{
try
{
await worker.InvokeVoidAsync("terminate");
}
catch (JSDisconnectedException)
{
// JS interop disconnected, worker is already gone
}

await worker.DisposeAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace Company.WebWorker1;

// Define [JSExport] methods here to run them in a Web Worker.
// Call them from your Blazor app using WebWorkerClient.InvokeAsync.
// Example: await worker.InvokeAsync<string>("Company.WebWorker1.WorkerMethods.Greet", ["World"]);

[SupportedOSPlatform("browser")]
public static partial class WorkerMethods
{
[JSExport]
public static string Greet(string name) => $"Hello, {name}!";
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
function withTimeout(promise, timeoutMs, timeoutMessage) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs));
return Promise.race([promise, timeout]);
}

class DotnetWebWorkerClient {
#worker;
Expand All @@ -10,29 +13,31 @@ class DotnetWebWorkerClient {
this.#worker = worker;
}

static create() {
return new Promise((resolve, reject) => {
const worker = new Worker('_content/Company.WebWorker1/dotnet-web-worker.js', { type: "module" });

worker.addEventListener('error', (e) => {
reject(new Error(e.message || 'Worker encountered an error'));
});
static create(initTimeoutMs, options = {}) {
const worker = new Worker('_content/Company.WebWorker1/dotnet-web-worker.js', { type: "module" });

const initWorker = new Promise((resolve, reject) => {
worker.addEventListener('error', (e) =>
reject(new Error(e.message || 'Worker encountered an error')));
worker.addEventListener('message', function onMessage(e) {
if (e.data.type === "ready") {
worker.removeEventListener('message', onMessage);
if (e.data.error) {
reject(new Error(e.data.error));
} else {
const client = new DotnetWebWorkerClient(worker);
client.#setupMessageHandler();
resolve(client);
}
e.data.error ? reject(new Error(e.data.error)) : resolve();
}
});
});

const dotnetJsUrl = DotnetWebWorkerClient.#resolveDotnetJsUrl();
const assemblyName = options?.assemblyName ?? null;
worker.postMessage({ type: 'init', dotnetJsUrl, assemblyName });

const dotnetJsUrl = DotnetWebWorkerClient.#resolveDotnetJsUrl();
worker.postMessage({ type: 'init', dotnetJsUrl });
return withTimeout(initWorker, initTimeoutMs, 'Worker initialization timed out').then(() => {
const client = new DotnetWebWorkerClient(worker);
client.#setupMessageHandler();
return client;
}, err => {
worker.terminate();
throw err;
});
}

Expand All @@ -43,23 +48,20 @@ class DotnetWebWorkerClient {
return import.meta.resolve?.(dotnetJsUrl) ?? dotnetJsUrl;
}

invoke(method, args) {
return new Promise((resolve, reject) => {
invoke(method, args, timeoutMs) {
const invoke = new Promise((resolve, reject) => {
const id = ++this.#requestId;
this.#pendingRequests[id] = { resolve: r => resolve(this.#parseIfJson(r)), reject };
this.#pendingRequests[id] = { resolve, reject };
this.#worker.postMessage({ method, args, requestId: id });
});
}

#parseIfJson(value) {
if (typeof value === 'string') {
try {
return JSON.parse(value);
} catch {
// not JSON, return as-is
return withTimeout(invoke, timeoutMs, `Worker method '${method}' timed out`).catch(err => {
const id = this.#requestId;
if (this.#pendingRequests[id]) {
delete this.#pendingRequests[id];
}
}
return value;
throw err;
});
}

terminate() {
Expand Down Expand Up @@ -96,6 +98,6 @@ class DotnetWebWorkerClient {
}
}

export function create() {
return DotnetWebWorkerClient.create();
export function create(initTimeoutMs, options) {
return DotnetWebWorkerClient.create(initTimeoutMs, options);
}
Loading
Loading