C#erから見たJavaScriptの非同期処理
解法
C# 開発者が JavaScript の非同期プログラミングとマルチスレッド処理を理解するには、以下の主要な違いと類似点に注目することが重要です:
- Promise と Task の対応関係
- 実行モデルの根本的な違いと並行処理
- Promise の then チェーンと await の違い
- Web Workers を使用したマルチスレッド処理
解説
Promise と Task の対応関係
C# の Task
に相当するのが JavaScript の Promise
です。両者とも非同期操作の結果を表現するオブジェクトですが、使い方に違いがあります。
C# の Task の例:
async Task DoAsync()
{
await Task.Run(() =>
{
// 重たい同期処理
});
}
JavaScript の Promise の例:
async function doAsync() {
await new Promise(resolve => {
// 重たい同期処理
resolve();
});
}
実行モデルの根本的な違いと並行処理
JavaScript のメインスレッドはシングルスレッドで動作しますが、Web Workers を使用することで並行処理やマルチスレッド処理が可能になります。以下に C# と JavaScript の実行モデルの違いを示します:
特性 | C# | JavaScript |
---|---|---|
実行モデル | マルチスレッドモデル | メインスレッドはシングルスレッド、Web Workers でマルチスレッド可能 |
並列処理 | 真の並列処理が可能(Parallel クラスなど) |
メインスレッドは並行処理、Web Workers で並列処理可能 |
同期コードのブロッキング | 同期メソッドは他のスレッドをブロックしない | メインスレッドの長時間実行コードはイベントループをブロック |
キャンセレーション |
CancellationToken を使用 |
AbortController を使用(比較的新しい機能) |
例外処理 | 非同期メソッド内の例外は自動的にTask にラップされる |
Promiseのreject を使用して例外を伝播する必要がある |
非同期ストリーム |
IAsyncEnumerable<T> を使用 |
Async Iteratorsを使用(比較的新しい機能) |
コンテキスト管理 |
ConfigureAwait(false) でコンテキスト切り替えを制御 |
コンテキスト切り替えの概念がない(シングルスレッドモデルのため) |
非同期初期化 | コンストラクタで非同期初期化を直接行えない | クラスのコンストラクタで直接await は使用できないが、即時実行非同期関数で回避可能 |
Web Workers を使用したマルチスレッド処理
JavaScript では、Web Workers を使用することで、メインスレッドとは別のバックグラウンドスレッドで JavaScript を実行できます。これにより、C#のマルチスレッド処理に近い並列処理が可能になります。
main.js
// worker.jsをロード
const worker = new Worker('worker.js');
// メッセージをWorkerに送信
worker.postMessage({data: 'some data'});
// Workerからメッセージを受け取る
worker.onmessage = function(event) {
console.log('結果: ', event.data);
};
// エラーハンドリング
worker.onerror = function(error) {
console.error('エラーが発生しました:', error.message);
};
worker.js
self.onmessage = function(e) {
// 重い処理を実行
const result = heavyComputation(e.data);
self.postMessage(result);
};
ただし、Web Workers 使用時には以下の点に注意が必要です:
- メインスレッドとWorker間のデータ転送にはコピーコストがかかります。
- 大量のデータを扱う場合は、
SharedArrayBuffer
やArrayBuffer
の転送(委譲)を使用してコピーコストを抑えることが重要です。
// SharedArrayBufferの使用例
const sharedBuffer = new SharedArrayBuffer(1024);
worker.postMessage({ sharedBuffer });
// ArrayBufferの転送例
const arrayBuffer = new ArrayBuffer(1024);
worker.postMessage({ arrayBuffer }, [arrayBuffer]);
C#開発者にとって、これはマルチスレッドプログラミングの経験を活かせる部分ですが、JavaScript特有の制約や最適化テクニックを理解する必要があります。
Promise の then チェーンと await の違い
JavaScript では、Promise を扱う方法として then
チェーンと await
の2つがあります。以下に両者の違いを示します:
特性 | then チェーン | await |
---|---|---|
構文の簡潔さ | 複雑な処理でネストが深くなる可能性あり | 同期コードに近い書き方で簡潔 |
エラーハンドリング |
.catch() メソッドを使用 |
通常の try-catch 構文を使用 |
制御フロー | 複雑な制御フローの表現が難しい | 通常の制御構造(if文、ループなど)を使用可能 |
デバッグ | 複雑なフローのデバッグが難しい場合がある | 同期コードに近いためデバッグが比較的容易 |
パフォーマンス | 理論上は若干有利 | 追加のPromiseラッピングによる微小なオーバーヘッドの可能性 |
並列処理 |
Promise.all() などで直接実装可能 |
Promise.all() と組み合わせて使用 |
then チェーンの例:
fetchData()
.then(data => processData(data))
.then(result => saveResult(result))
.catch(error => handleError(error));
await の例:
async function handleData() {
try {
const data = await fetchData();
const result = await processData(data);
await saveResult(result);
} catch (error) {
handleError(error);
}
}
await
を使用すると、コードがより読みやすく、同期コードに近い書き方ができます。一方で、then
チェーンは並列処理を表現する際に有用です。
補足情報
-
JavaScript のメインスレッドはシングルスレッドですが、Web Workers を使用することで並列処理が可能です。ただし、DOM操作などはメインスレッドでのみ可能です。
-
Web Workers を使用する際は、スレッド間通信のオーバーヘッドを考慮する必要があります。大量のデータを扱う場合は、
SharedArrayBuffer
やArrayBuffer
の転送を検討してください。 -
C# の
Task.Run()
に相当する機能として、Web Workers を使用できますが、使用方法と制約が異なります。 -
JavaScript のイベントループモデルは、C# の非同期プログラミングモデルとは根本的に異なります。長時間実行される処理は Web Worker に移動するなどして、メインスレッドのブロックを避けることが重要です。
これらの違いを理解することで、C# 開発者は JavaScript の非同期プログラミングとマルチスレッド処理をより効果的に習得し、両言語の強みを活かした開発が可能になります。
Discussion
最初の js の
function
をその前にある c# の方と揃えてasync function
にしてPromise
をawait
するか、 c# の方をasync
外して ContinueWithするようにしたほうが良かったのではjuner さんと同じような指摘かもですが、最初の「C# の Task の例:」が ”Task を使う例” であるのに対して、「JavaScript の Promise の例:」は ”Promise を作る(作って使う)例” なのが非対称なのかなと思います。
下のような感じなら Promise を使う例 になると思います。
juner さんの前者方式に統一しつつ、内容もできるだけ等価にしてみたつもりですが、こんな感じですかね?
javascript の メインスレッドはシングルスレッドではありますが、並行処理は
Worker
を立てることでマルチスレッド処理もできたりします(ただし、postMessage
のコピーコストとか問題があるのでSharedArrayBuffer
使うとかArrayBuffer
を委譲するとかして コピーコスト抑えるのが肝心だったりします。ありがとうございます!
教えていただいた内容を読んでみて、理解の上、記事に反映したいと思います!
あざます!
追記しました!Workerについてはあくまで雰囲気程度で!