# 通知設計書 25-EventNotify.SendNotification

## 概要

本ドキュメントは、System.Speech.Internal.SapiInterop.EventNotifyクラスにおけるSendNotificationメソッドの設計仕様を定義する。SendNotificationは、Windows SAPI（Speech API）音声認識エンジンからのイベントを受信し、アプリケーションのスレッドモデルに適合した形でディスパッチする内部通知機能である。

### 本通知の処理概要

SendNotificationは、SAPIの非同期音声認識イベント（認識結果、仮説、オーディオ状態変更など）をマネージドコードにブリッジするための内部メカニズムである。ISpNotifySink COMインターフェースを通じてSAPIからの通知を受信し、スレッドプールワーカースレッド上でイベントを処理してRecognizerBaseにディスパッチする。

**業務上の目的・背景**：Windows音声認識機能を.NETアプリケーションで利用する場合、SAPI COMコンポーネントからの非同期イベントを適切に処理する必要がある。SAPIはCOMスレッディングモデルに従ってイベントを発生させるため、.NETのスレッドモデルと整合させるためのブリッジ層が必要である。EventNotify.SendNotificationはこの役割を担い、音声認識イベントをアプリケーションに伝播させる。

**通知の送信タイミング**：SAPIのISpEventSourceからイベントが発生し、ISpNotifySink.Notify()が呼び出されたタイミングで、ThreadPool.QueueUserWorkItemを通じてSendNotificationがスケジュールされる。

**通知の受信者**：内部的にはIAsyncDispatch実装（AsyncSerializedWorker）を通じてRecognizerBaseにイベントが伝播される。最終的にはSpeechRecognizer/SpeechRecognitionEngineのイベント（SpeechRecognized、SpeechHypothesizedなど）としてアプリケーションに通知される。

**通知内容の概要**：SpeechEventオブジェクトの配列が渡され、各SpeechEventには認識結果、仮説、オーディオ状態、文法状態などの情報が含まれる。

**期待されるアクション**：IAsyncDispatch.Post()を通じてイベントがキューイングされ、RecognizerBaseで処理されてユーザーイベントとして発火される。

## 通知種別

内部システム通知（COM相互運用イベントブリッジ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（ThreadPool経由でワーカースレッドにキューイング） |
| 優先度 | 中（音声認識イベントの標準配信） |
| リトライ | 無（イベントは一度のみ処理） |

### 送信先決定ロジック

IAsyncDispatch実装（通常はAsyncSerializedWorker）にSpeechEventの配列がPost()される。AsyncSerializedWorkerはイベントをシリアライズしてRecognizerBaseに配信する。

## 通知テンプレート

### メソッドシグネチャ

```csharp
internal void SendNotification(object ignored)
```

### SpeechEventオブジェクト

```csharp
internal class SpeechEvent
{
    internal SPEVENTENUM EventId;       // イベント種別
    internal ulong AudioPosition;       // オーディオ位置
    internal object EventData;          // イベント固有データ
    // その他のプロパティ
}
```

### 本文テンプレート

```
通知メソッド: SendNotification
呼び出し元: SpNotifySink.Notify() -> ThreadPool.QueueUserWorkItem
処理内容:
  1. ISpEventSourceからイベントを取得
  2. SpeechEventオブジェクトを生成
  3. IAsyncDispatch.Post()でディスパッチ
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| ignored | 未使用パラメータ（WaitCallback互換性のため） | ThreadPool.QueueUserWorkItem | No |
| speechEvents | 処理対象のSpeechEventリスト | SpeechEvent.TryCreateSpeechEvent | Yes |
| _sapiEventSourceReference | SAPIイベントソースへの弱参照 | コンストラクタで設定 | Yes |
| _dispatcher | イベントディスパッチャー | コンストラクタで設定 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| COMコールバック | ISpNotifySink.Notify() | EventNotifyが有効 | SAPIがイベントを発生 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| EventNotifyがDispose済み | _sapiEventSourceReferenceがnullの場合 |
| ISpEventSourceが無効 | WeakReferenceのTargetがnullの場合 |
| イベントなし | SpeechEvent.TryCreateSpeechEventがnullを返す場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[SAPI音声認識イベント発生] --> B[ISpEventSource.SetNotifySinkで登録されたSinkに通知]
    B --> C[SpNotifySink.Notify呼び出し]
    C --> D[WeakReferenceからEventNotify取得]
    D -->|EventNotify有効| E[ThreadPool.QueueUserWorkItem]
    D -->|EventNotify無効| F[処理終了]
    E --> G[SendNotification実行]
    G --> H[lockで排他制御]
    H --> I{_sapiEventSourceReference != null}
    I -->|Yes| J[ISpEventSourceからイベント取得ループ]
    I -->|No| K[処理終了]
    J --> L[SpeechEvent.TryCreateSpeechEvent]
    L -->|イベントあり| M[speechEventsリストに追加]
    L -->|イベントなし| N[ループ終了]
    M --> J
    N --> O[_dispatcher.Post実行]
    O --> P[RecognizerBaseでイベント処理]
    P --> Q[ユーザーイベント発火]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（音声認識イベント処理であり、データベースは使用しない）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| NullReferenceException | EventNotifyがDispose済み | WeakReferenceチェックで防止 |
| COMException | SAPIとの通信エラー | 例外をキャッチしてログ記録 |
| InvalidOperationException | 不正な状態でのアクセス | lockで排他制御 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（イベントは一度のみ処理） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 制限なし | イベントは発生順に処理される |

### 配信時間帯

制限なし（リアルタイム配信）

## セキュリティ考慮事項

- WeakReferenceを使用してEventNotifyへの循環参照を防止
- lockステートメントで排他制御を行い、スレッドセーフ性を確保
- SAPIはネイティブCOMコンポーネントであり、適切な権限が必要

## 備考

- SendNotificationのパラメータ`ignored`はWaitCallbackデリゲートの互換性のために存在し、実際には使用されない
- EventNotifyはDisposableパターンを実装しており、Dispose()でSAPIからの通知登録を解除する
- _additionalSapiFeaturesフラグでSAPI 5.3固有の機能を制御

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

EventNotifyクラスとSpNotifySinkクラスの構造を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | EventNotify.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/EventNotify.cs` | 行30-121: EventNotifyクラス全体 |
| 1-2 | EventNotify.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/EventNotify.cs` | 行10-27: SpNotifySinkクラス |

**読解のコツ**: EventNotifyはISpEventSourceからイベントを受信するためのブリッジ。SpNotifySinkはISpNotifySinkインターフェースを実装しCOMからのコールバックを受ける。

#### Step 2: 通知メカニズムを理解する

Notify()からSendNotification()への流れを追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | EventNotify.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/EventNotify.cs` | 行17-24: ISpNotifySink.Notify()実装 |
| 2-2 | EventNotify.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/EventNotify.cs` | 行72-92: SendNotification実装 |

**主要処理フロー**:
- **行18-19**: WeakReferenceからEventNotifyを取得
- **行22**: ThreadPool.QueueUserWorkItemでSendNotificationをスケジュール
- **行74**: lockでスレッドセーフに
- **行77-79**: ISpEventSourceの有効性確認
- **行82-87**: イベント取得ループ
- **行88**: _dispatcher.Post()でイベントディスパッチ

#### Step 3: 初期化とクリーンアップを理解する

コンストラクタとDispose()の処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | EventNotify.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/EventNotify.cs` | 行34-45: コンストラクタ |
| 3-2 | EventNotify.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/EventNotify.cs` | 行52-70: Dispose実装 |

**主要処理フロー**:
- **行37-40**: WeakReferenceとdispatcherの設定
- **行43-44**: SpNotifySinkの作成とSAPIへの登録
- **行60-64**: SetNotifySink(null)で通知解除
- **行68**: _sapiEventSourceReference = null

### プログラム呼び出し階層図

```
[SAPI COMコンポーネント] 音声認識イベント発生
    |
    v
ISpEventSource.SetNotifySink() で登録されたSink
    |
    v
SpNotifySink.Notify() (行17)
    |
    ├─ _eventNotifyReference.Target (行19)
    |   └─ WeakReferenceからEventNotify取得
    |
    └─ ThreadPool.QueueUserWorkItem (行22)
           |
           v
       SendNotification (行72)
           |
           ├─ lock(this) (行74)
           |
           ├─ _sapiEventSourceReference.Target (行79)
           |   └─ ISpEventSource取得
           |
           ├─ SpeechEvent.TryCreateSpeechEvent ループ (行84)
           |   └─ SpeechEventリスト作成
           |
           └─ _dispatcher.Post(speechEvents.ToArray()) (行88)
                  |
                  v
              AsyncSerializedWorker.Post()
                  |
                  v
              RecognizerBase イベント処理
                  |
                  v
              ユーザーイベント発火
              (SpeechRecognized, SpeechHypothesized等)
```

### データフロー図

```
[入力]                      [処理]                              [出力]

SAPI ISpEventSource   ───▶  SpNotifySink.Notify()       ───▶  ThreadPoolにキューイング
音声認識イベント               (行17-24)
    |                                                            |
    v                                                            v
COM callback          ───▶  SendNotification()          ───▶  SpeechEvent配列作成
                            (行72-92)                           |
                                                                 v
                                                          _dispatcher.Post()
                                                                 |
                                                                 v
                                                          RecognizerBase処理
                                                                 |
                                                                 v
                                                          SpeechRecognizedイベント
                                                          SpeechHypothesizedイベント
                                                          AudioStateChangedイベント等
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| EventNotify.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/EventNotify.cs` | ソース | EventNotify/SpNotifySink実装（行10-121） |
| SapiRecoContext.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/SapiRecoContext.cs` | ソース | EventNotify作成（行70-72） |
| RecognizerBase.cs | `src/libraries/System.Speech/src/Recognition/RecognizerBase.cs` | ソース | イベント処理基底クラス |
| SpeechEvent.cs | `src/libraries/System.Speech/src/Internal/SapiInterop/SpeechEvent.cs` | ソース | SpeechEventクラス定義 |
