افزایش کارایی و پاسخگویی برنامههای Blazor WebAssembly با استفاده از Blazor Web Worker در داتنت 11
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۳/۳۱ ۰۹:۳۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
blazorwebworker ساختار فوقالعاده راحتی فراهم کرده تا شما بتوانید کدهای #C خود را مستقیماً درون یک Web Worker واقعی مرورگر اجرا کنید. داتنت خودش تمام زیرساختها، ارتباطات بین نخ اصلی و نخ Worker (مسائل مربوط به Serialization و Interop) را مدیریت میکند و به شما یک کلاینت تمیز به نام WebWorkerClient با پشتیبانی از خطایابی، لغو عملیات (CancellationToken) و Timeout میدهد.| وجه تمایز | IHostedService در Blazor WASM | Blazor Web Worker |
| محل اجرا (Thread) | روی همان نخ اصلی UI اجرا میشود. | روی یک نخ پسزمینه واقعی (Separate Thread) اجرا میشود. |
| هدف اصلی | مدیریت چرخهی حیات برنامه، کارهای دورهای سبک (مانند زدن به API هر ۵ دقیقه). | انجام محاسبات سنگین و طولانیمدت بدون بلاک کردن UI. |
| تأثیر بر پاسخگویی UI | اگر کار سنگینی انجام دهد، رابط کاربری قفل میشود. | رابط کاربری کاملاً پاسخگو (Responsive) باقی میماند. |
| نحوه ارتباط | دسترسی مستقیم به کانتینر DI و وضعیت برنامه دارد. | از طریق پیامرسانی ساختاریافته (InvokeAsync) با نخ اصلی تبادل داده میکند. |
WebWorkerClient مبتنی بر الگوی کارخانه (Factory Pattern)، مدیریت طول عمر و فراخوانی متدهای Worker را به شدت ساده کرده است.InvokeVoidAsync: اضافه شدن قابلیت Fire-and-forget برای زمانهایی که نیاز به فراخوانی یک متد بدون بازگشت هیچگونه مقداری از سمت Worker است.CancellationToken به فرآیند ساخت یا اجرای متدها در Worker، جهت از بین بردن تمیزِ (Tear down) سرویسهای پسزمینهای که دچار بنبست (Stuck) شدهاند.dotnet new blazorwebworker -o MyApp.Worker
WebWorkerClient برای ساخت ورکر و فراخوانی متدهای سیشارپی که با اتریبیوت [JSExport] در پروژه ورکر نشانهگذاری شدهاند، استفاده میشود:@page "/heavy-computation"
@inject IJSRuntime JSRuntime
<h3>پردازش سنگین در نخ پسزمینه</h3>
<button @onclick="RunHeavyTask">شروع محاسبات سنگین</button>
<p>نتیجه: @_result</p>
@code {
private string _result = "آماده برای پردازش...";
private async Task RunHeavyTask()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // تنظیم تایماوت ۳۰ ثانیهای
// ایجاد یک نمونه از ورکر به صورت آسنکرون
await using var worker = await WebWorkerClient.CreateAsync(JSRuntime, cts.Token);
_result = "در حال محاسبه...";
// فراخوانی متد سیشارپ مستقر در Web Worker بدون مسدود شدن UI
_result = await worker.InvokeAsync<string>(
"MyApp.MyWorker.Greet",
["World"],
cts.Token
);
}
}DisposeAsync آن را متوقف سازید تا از نشت حافظه (Memory Leak) جلوگیری شود.Blazor Web Worker در داتنت 11، مرزهای کارایی برنامههای تحت وب مبتنی بر WebAssembly را جابجا کرده است. این قابلیت با فراهم کردن امکان انتقال پردازشهای محاسباتی سنگین به نخهای مجزا، تضمین میکند که رابط کاربری برنامههای Blazor همواره نرم و پاسخگو باقی بماند. ترکیب این ابزار (برای کارهای سنگین محاسباتی) در کنار IHostedService (برای مدیریت چرخهی حیات فرآیندهای سبک دورهای)، جعبه ابزار توسعهدهندگان داتنت را برای خلق برنامههای وب در سطح Enterprise کاملتر از همیشه کرده است.Blazor Web Worker در داتنت 11، این مقاله به بررسی یک سناریوی واقعی میپردازد: جایگزینی فرآیند دریافت دادههای هواشناسی با یک الگوریتم محاسباتی سنگین محاسباتی در پسزمینه. در ادامه، نحوه پیکربندی، ساختار متدهای صادراتی ([JSExport]) و پیادهسازی الگوی بهینه کشینگ (Caching) برای جلوگیری از بارگذاری مکرر رانتایم داتنت را بررسی میکنیم.dotnet new blazorwebworker، برای استفاده از قابلیتهای پیشرفته پل ارتباطی بین داتنت و جاوااسکریپت، باید اجازه استفاده از کدهای ناامن (Unsafe Code) را در فایل پروژه .csproj برنامه اصلی Blazor فعال کنیم:<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BlazorWebWorker\BlazorWebWorker.csproj" />
</ItemGroup>
</Project>static partial تعریف شده، با اتریبیوت [JSExport] نشانهگذاری شوند و خروجی آنها ترجیحاً از نوع انواع داده اولیه (Primitive) یا string (به صورت متون سریالایز شده JSON) باشد.using System.Text.Json;
using System.Runtime.Versioning;
using System.Runtime.InteropServices.JavaScript;
namespace BlazorWebApp;
[SupportedOSPlatform("browser")]
public static partial class WorkerMethods
{
[JSExport]
public static string GetForecasts(int count)
{
// شبیهسازی یک پردازش سنگین محاسباتی که اجرای آن ۵ ثانیه زمان میبرد.
// اگر این کد روی نخ اصلی اجرا میشد، مرورگر کاملاً قفل (Freeze) میشد.
Thread.Sleep(5_000);
// تولید دادههای خام هواشناسی
var forecasts = Enumerable.Range(1, count).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = "تولید شده در وبورکر"
}).ToArray();
// تبدیل به رشته متنی جهت انتقال امن از فنس حافظه ورکر به مروگر
return JsonSerializer.Serialize(forecasts);
}
}_framework/dotnet.js و اسمبلیها) در یک امپدانس حافظه مستقل است، متد CreateAsync عملیاتی زمانبر به شمار میرود. برای غلبه بر این اورهد (Overhead)، استفاده از یک الگوی کشینگ استاتیک بسیار حیاتی است:public static class WorkerClientContainer
{
private static WebWorkerClient? _cachedClient;
public static async Task<WebWorkerClient> GetOrCreateClientAsync(IJSRuntime jsRuntime)
{
// بازگرداندن کلاینت موجود در صورت مقداردهی قبلی
if (_cachedClient is not null)
{
return _cachedClient;
}
// ایجاد نمونه برای اولین بار (هزینهبر اما فقط یکبار)
_cachedClient = await WebWorkerClient.CreateAsync(jsRuntime);
return _cachedClient;
}
}@page "/weather"
@using BlazorWebWorker
@inject IJSRuntime JsRuntime
<h3>وضعیت آب و هوا (پردازش در ورکر)</h3>
@if (forecasts == null)
{
<p><em>در حال محاسبات سنگین در نخ پسزمینه (UI قفل نشده است)...</em></p>
}
else
{
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// دریافت کلاینت ورکر از کش
var worker = await WorkerClientContainer.GetOrCreateClientAsync(JsRuntime);
// مشخص کردن آدرس دقیق متد صادراتی داتنت
const string methodSignature = "BlazorWebApp.WorkerMethods.GetForecasts";
const int requestedRows = 5;
// فرستادن درخواست به ورکر. متد InvokeAsync به صورت خودکار
// خروجی رشته را به آبجکت سیشارپ دیسریالایز میکند.
forecasts = await worker.InvokeAsync<WeatherForecast[]>(methodSignature, args: [requestedRows]);
}
}dotnet-web-worker-client.js: در نقش کلاینتِ نخ اصلی عمل کرده، سیستم شناسهگذاری درخواستها (requestId) را هندل میکند و مدیریت Promiseها را به عهده دارد.dotnet-web-worker.js: به عنوان هسته اصلی ورکر مستقر در پسزمینه عمل میکند. این اسکریپت رانتایم سبک داتنت را لود کرده، با متد getAssemblyExports متدهای مجاز سیشارپ را استخراج میکند و از طریق self.postMessage پاسخها را در قالب Transferable Objects (بدون کپی شدنهای سنگین در حافظه) به نخ اصلی پس میفرستد.