【C#】ローカルで作成した待機ハンドルをサービスで使用する

ユーザーがログインしているアカウント上で起動しているソフトで生成した待機ハンドルを同じPC上のサービス (LocalSystem = NT AUTHORITY\SYSTEM) 上で利用する方法の紹介です。

通常、クライアントが作成した名前付き待機ハンドル (EventWaitHandle) をサービス上で OpenExisting メソッドで取得しようとすると以下のような例外が発生します。

System.Threading.WaitHandleCannotBeOpenedException:

指定された名前のハンドルは存在しません。

これを回避するためにはユーザー側で EventWaitHandle を作成するときにハンドル名の先頭に "Global\" を追加します(たったこれだけで待機ハンドル共有できるようになります)

補足:

AutoResetEventManualResetEvent は名前が指定できない(同一プロセス内の利用限定) のため名前を指定できる EventWaitHandle を使用します。

実装例

// クライアント側

public void Client()
{
    // 待機ハンドルの名前の先頭にGlobal\を指定する
    string name = $@"Global\{Guid.NewGuid().ToString()}";
    
    using (var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, name))
    {
        Service(name); // サービスに処理要求を投げる

        // サービス側でシグナル状態にされる or タイムアウトするまで待機する
        if (waitHandle.WaitOne(5000)
        {
            Console.WriteLine("OK");
        }
        else
        {
            Console.WriteLine("Timeout"); // 5秒以内に解除されなかった場合
        }
    }
}

サービス側の実装は以下の通りです。

Global\ プレフィックスが付いていればそのまま OpenExisting や TryOpenExisting でハンドルが取得できます。

// サービス側

public void Servicec(string name)d
{
    Task.Run(() =>
    {
        try
        {
            var isOk = EventWaitHandle.TryOpenExisting(name, out var result);
            if (isOk)
            {
                result.Set();
            }
            else
            {
                // もし取れない場合クライアント側でタイムアウトを待ち
            }
        }
    }
}