Blazor WebAssemblyでマルチスレッドを使っていくための準備 （.NET 8を待っています）

# はじめに

今年は.NET 7でBlazor WebAssemblyでマルチスレッドがサポートされそうということで、このIssueをぼんやりと眺める日々を過ごしていました。

https://github.com/dotnet/aspnetcore/issues/17730

8月に.NET 7ではマルチスレッドに対応しないという内容が投稿され残念に思っていたら、.NET 7 RC2のリリースノートでマルチスレッドに関する内容に言及がありました。

https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-7-rc-2/

> `wasm-experimental`ワークロードは[Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)を使用したWebAssembly上のマルチスレッドの.NETアプリケーションをサポートするようになりました。これは新しい .NET ランタイム機能です。マルチスレッドのサポートはBlazor WebAssemblyアプリにはまだ統合されていませんが（.NET 8で予定）、実験的なWebAssembly Browser Appテンプレートを使ってプレビュー形式で試すことが可能です。

私は.NET 7 RC2からBlazor WebAssemblyでマルチスレッドを試すことができるのだと勘違いしていたのですが、試すことができるのは`wasmbrowser`のテンプレートでした。すみません。

それでも部分的にマルチスレッドを試すことができるのであれば、きっと.NET 8がリリースされてBlazor WebAssemblyにマルチスレッドを導入するときに役立つだろうと思い、動かしてみることにしました。

# .NET 7 RC2のリリースノートで紹介されている例を動かしてみる

さきほどの.NET 7 RC2のリリースノートで紹介されている例を手元で動かしてみます。

`wasm-experimental`ワークロードをインストールします。

```powershell
dotnet workload install wasm-experimental
```

アプリを作成します。

```powershell
dotnet new wasmbrowser -o sampleapp1
```

プロジェクトファイルの`<PropertyGroup>`に`<WasmEnableThreads>`を追加します。

```xml:sampleapp1.csproj
<PropertyGroup>
  ...
  <WasmEnableThreads>true</WasmEnableThreads>
</PropertyGroup>
```

NuGetで`Microsoft.NET.WebAssembly.Threading`をインストールします。

```powershell
dotnet add package --prerelease Microsoft.NET.WebAssembly.Threading
```

`program.cs`に.NET 7 RC2のリリースノートのコードを反映させます。テンプレートが作成したJavaScript関係のコードをさわると、他の部分も編集する必要があるので、ここではそのままにしています。

```csharp:program.cs
using System;
using System.Threading;
using System.Runtime.Versioning;
using System.Runtime.InteropServices.JavaScript;

[assembly: SupportedOSPlatform("browser")]

new Thread(SecondThread).Start();
Console.WriteLine($"Hello, Browser from the main thread {Thread.CurrentThread.ManagedThreadId}");

static void SecondThread()
{
    Console.WriteLine($"Hello from Thread {Thread.CurrentThread.ManagedThreadId}");
    for (int i = 0; i < 5; ++i)
    {
        Console.WriteLine($"Ping {i}");
        Thread.Sleep(1000);
    }
}

public partial class MyClass
{
    [JSExport]
    internal static string Greeting()
    {
        var text = $"Hello, World! Greetings from {GetHRef()}";
        Console.WriteLine(text);
        return text;
    }

    [JSImport("window.location.href", "main.js")]
    internal static partial string GetHRef();
}
```

`dotnet run`で実行して表示されたURLにアクセスすると、コンソールに以下のように出力されます。

![console1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/70a1fe0a-b116-542f-73b6-582d21ea01ae.png)

メインスレッドとは別に作成されたスレッドで`Ping 0` `Ping 1` `Ping 2`と出力されています。

# 負荷をかけてみる

ここまでは.NET 7 RC2のリリースノートのコードをそのまま実行しました。マルチスレッドが必要なケースはCPUに負荷がかかるような場合が多いと思うので、そういった例を試してみます。さきほどの`program.cs`を編集して無限ループで乱数を生成するようにします。

```csharp:program.cs
static void SecondThread()
{
    Console.WriteLine($"Hello from Thread {Thread.CurrentThread.ManagedThreadId}");
    var random = new Random();
    while (true)
    {
        random.Next();
    }
}
```

## スレッドが1個のとき

`SecondThread`を呼び出した回数だけ乱数を生成するスレッドが作られます。私の8コア16スレッドCPUで1回だけ呼び出した場合は以下のようになりました。タスクマネージャーに表示されたCPUの使用率は10%前後でした。

![multithreading1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/d688f98e-0210-9ee1-b895-af9841c2b9cb.png)

## スレッドが8個のとき

`new Thread(SecondThread).Start();`を8個並べて、スレッドが8個のときは以下のようになりました。タスクマネージャーに表示されたCPUの使用率は30%前後でした。

![multithreading8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/422f9afa-1e6e-1876-7019-9e109a9f498a.png)

タスクマネージャーの画面下半分のCPUが活躍しているようで、マルチコアが活用されていることがわかります。

## CPUのコア数を知るには？

マルチスレッドを使う場合にCPUのコア数を把握したい場合があると思うのですが、通常は`System.Environment.ProcessorCount`を見ればよいようです。

https://www.tetsuyanbo.net/tetsuyanblog/34731

ただ、今回のコードの中で`System.Environment.ProcessorCount`の値を確認してみると`1`となっていたので、これは何か別の方法がありそうです。

# おわりに

Blazor WebAssemblyでマルチスレッドを使うのは.NET 8を待つ必要がありますが、現時点で試せる範囲でマルチスレッドを動かしてみました。マルチコアを活用することも少しだけ体験できましたし、.NET 8を楽しみに待ちたいと思います。