「応答なし」の正体 ── Windowsがアプリを「固まった」と判定する仕組みと、固まらない設計

· 更新日: · · Windows, Windows開発, 不具合調査, マルチスレッド, WinForms, WPF, Win32 API, UI設計

更新履歴(1件・最終更新 2026年09月06日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の主張を変えず、OSの判定・原因・非同期設計・回避策の限界・調査手順を分け、比較表と小見出しで整理した。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.22170898)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「「応答なし」の正体 ── Windowsがアプリを「固まった」と判定する仕組みと、固まらない設計」合同会社小村ソフト. https://comcomponent.com/blog/windows-app-not-responding-hang-mechanism/

DOI(登録済みアーカイブ)
10.5281/zenodo.22170898
DOI(前回登録した版)
10.5281/zenodo.22170899

「処理中に画面が白くなり、タイトルに『応答なし』と出る」「利用者からは『たまに固まる』と言われるのに、開発機では再現しない」── Windowsの業務アプリでよくある困りごとです。

最初に押さえたいのは、「応答なし」を表示しているのはアプリ自身ではなく、Windowsであるという点です。Windowsはウィンドウのメッセージ処理が止まったことを検知し、元の画面を代わりのウィンドウに差し替えます。原因を追う鍵は、そのウィンドウを担当するUIスレッドが、何をしていて次のメッセージを処理できないのかにあります。12

この記事では、Windowsで業務アプリを作る開発者と、固まるアプリの問い合わせを受ける情シス担当者に向けて、OSの判定 → メッセージループ → 原因別の切り分け → 固まらない設計 → 調査手順の順に整理します。いま固まっているアプリを調べたい場合は、先に7章を読んでください。

1. まず結論:「表示を消す」ではなく「UIスレッドを空ける」

「応答なし」の仕組み、直し方、調べ方は、次のように分けると見通しがよくなります。

知りたいこと・困っていること 最初に押さえること 詳しい説明
Windowsは何を見て応答なしと判定するのか ウィンドウと、その所有GUIスレッドのメッセージ処理を見る。プロセス全体の判定ではない 2章
なぜボタンも再描画も止まるのか UIスレッドがイベント処理や待機から戻れず、次のメッセージを取り出せないため 3・4章
時間のかかる処理で画面を固めたくない CPU処理・同期しかないAPIはワーカーへ分離し、I/Oは非同期APIを使う 5章
DoEventsや設定で表示だけ回避してよいか 再入バグを招いたり、表示を隠すだけになったりする。根本対策にはしない 6章
たまに固まる原因を調べたい 終了・再起動する前に、固まっている瞬間のダンプを取る 7章

対策の原則は、UIスレッドで長く待たない・重い計算をしないことです。UIスレッドは入力、描画、進捗表示、キャンセル受付を担当し、時間のかかる仕事から切り離します。3

また、「応答なし」と表示されるまでの時間と、快適に操作できる応答時間は別物です。5秒未満ならよいのではありません。この違いは4.5で説明します。

2. 「応答なし」はOSの判定:5秒ルールと代わりの画面

2.1 判定の単位はプロセス全体ではなく、ウィンドウと所有スレッド

Microsoftの IsHungAppWindow の説明では、次の条件を満たすウィンドウを応答なしとみなします。1

条件 内容
入力待ちではない 入力を待っている状態ではない
起動処理中ではない アプリの起動処理中ではない
メッセージを取り出していない 内部タイムアウトの5秒間、PeekMessage を呼んでいない

OSは、アプリが何を計算しているかではなく、メッセージループが回っているかを見ています。メッセージループとは、入力や再描画の要求を順番に取り出して処理する仕組みです。具体的な動作は3章で説明します。

5秒という値は将来変わる可能性があることも、同じ資料に明記されています。これは応答なし判定の内部タイムアウトであり、「UIを5秒まで止めてよい」という設計基準ではありません。1

判定はプロセス単位ではありません。複数のUIスレッドを持つアプリでは、あるウィンドウが固まっていても、別スレッドが担当するウィンドウは動いていることがあります。調査でも、単にプロセスを見るだけでなく、固まったウィンドウの所有スレッドを特定する必要があります。

2.2 白くかすんだ画面は「ゴーストウィンドウ」

トップレベルウィンドウが応答なしと判定されると、Windowsは元のウィンドウを非表示にし、同じZオーダー・位置・サイズ・外観を持つゴーストウィンドウに差し替えます。タイトルの「(応答なし)」や、Aeroテーマで白くかすむ見た目は、固まったアプリではなく、この代わりの画面によるものです。2

画面の状態 ユーザーから見えること
元のアプリのウィンドウ メッセージを処理できず、クリックや再描画に反応しない
OSが用意したゴーストウィンドウ 移動・サイズ変更・最小化・閉じるといった限られた操作ができる

代わりのウィンドウを動かせても、アプリの中身が動き出したわけではありません。OSが最低限の操作を肩代わりしているだけです。24

「表示が出るのが早すぎる」という問い合わせも、まずはUIスレッドが長時間メッセージ処理に戻れていない問題として捉えます。表示の抑止より、止まっている処理を調べるのが先です。

2.3 デバッグ中に表示されなくても、固まっていないとは限らない

デバッガー接続中は、OSがゴーストウィンドウを生成しません。そのため、「デバッグ実行では出ず、通常実行では応答なしになる」という違いがあっても、同じようにUIスレッドが塞がっていて、表示だけが違う場合があります。2

ゴーストウィンドウへの差し替えをプロセス単位で無効にするAPIもありますが、固まりそのものを直すAPIではありません。6.2で用途を分けて説明します。

3. なぜ画面が止まるのか:UIスレッドとメッセージループ

3.1 入力も再描画も、同じスレッドが処理する

WindowsのGUIアプリはイベント駆動です。マウス、キーボード、再描画要求、タイマーなどのメッセージを受け取り、それぞれに対応する処理を実行します。ウィンドウを作った各スレッドはメッセージキューを持ち、次のようなループを回します。5

MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);   // ウィンドウプロシージャが呼ばれる
}

GetMessage がキューからメッセージを取り出し、DispatchMessage がウィンドウのウィンドウプロシージャを呼びます。ウィンドウプロシージャは、メッセージに対応する処理を行う関数です。ボタンのクリック処理や再描画、WinForms・WPFのイベントハンドラーも、このUIスレッドでのメッセージ処理につながっています。6

メッセージループの基本構造OSがマウスやキーボードなどの入力をスレッドのメッセージキューに入れ、UIスレッドのループがGetMessageで取り出してDispatchMessageでウィンドウプロシージャを呼び、処理が終わるとループの先頭に戻るOS(入力・再描画要求・タイマー)スレッドのメッセージキューGetMessageで取り出すDispatchMessageウィンドウプロシージャで処理

図1: メッセージを取り出し、ウィンドウプロシージャで処理し、ループに戻る。この回転が入力と描画を支える。

ここで、クリックハンドラーの中から長い処理を呼ぶとどうなるでしょうか。その処理が戻るまで、UIスレッドは次のメッセージを取り出せません。後から届いたクリックも再描画要求も処理できなくなります。これが「画面が固まる」基本構造です。

3.2 PostMessageとSendMessageは、戻るタイミングが違う

メッセージの配送には、キューに積む経路と、処理の完了を待つ経路があります。57

API 動作 呼び出し元が戻るタイミング
PostMessage メッセージをキューに積み、受信側が取り出して処理する キューに積んだら戻る。処理完了は待たない
SendMessage ウィンドウプロシージャにメッセージを送り、処理を実行させる ウィンドウプロシージャの処理が終わるまで戻らない

とくに別スレッドのウィンドウに SendMessage を送る場合、受信側がメッセージを処理できるまで送信側も待たされます。この違いが、4.3のデッドロックに直結します。7

4. 原因別に見る:UIスレッドを塞ぐ5つのパターン

見た目が同じ「応答なし」でも、塞いでいるものは違います。まず候補を分け、最終的には7章のダンプやトレースで確かめます。

原因の候補 UIスレッドで起きていること よくある現れ方
同期I/O・ネットワーク呼び出し ファイル、DB、Web APIなどの完了を待つ 開発機では速いが、本番や特定環境で固まる
ロック待ち ワーカーが持つロックを取得できず待つ 処理の重なり方によって、まれに固まる
スレッド間の SendMessage 別スレッドがメッセージを処理するのを待つ 相互待ちやブロードキャストに巻き込まれる
COMのSTAへの呼び出し 呼び出し先STAのメッセージ処理を待つ UIスレッドの停止が他スレッドのCOM呼び出しにも波及する
短い処理の積み重ね 1回は短くても、連続実行でループへ戻らない 件数が増えたときだけ操作が重くなる

4.1 同期I/Oとネットワーク呼び出し

最もよく出会うのが、ボタンのクリックハンドラーで大きなファイルの読み書き、データベースクエリ、Web API、ネットワークドライブへのアクセスを同期で行うパターンです。

開発機ではコンマ数秒で終わっても、本番のネットワーク遅延やファイルサーバーの不調で、数十秒の待ちになることがあります。ネットワークドライブは接続断時のタイムアウトが長く、症状を悪化させます。「開発機で速かった」は、UIスレッドで待ってよい理由にはなりません。

4.2 ワーカーが握ったロックをUIスレッドが待つ

共有データを守るロックも、UIを止める原因になります。ワーカーが長時間ロックを持ち、UIスレッドもそのロックを取ろうとすると、UIスレッドは取得できるまで待ってしまいます。

仕事をワーカーに移しても、UIスレッドがその仕事の終了やロック解放を待つ構造では、画面は空きません。ロックの規律はマルチスレッド実務シリーズで詳しく扱っています。

4.3 SendMessageで互いの完了を待つ

定番のデッドロックは、UIスレッドがワーカーの終了を待ち、そのワーカーがUIへ SendMessage を送って待つ組み合わせです。UIは待機中でメッセージを処理できず、ワーカーも SendMessage から戻れないため、両者が先へ進めません。75

スレッド間SendMessageによるデッドロックUIスレッドがワーカースレッドの結果待ちでブロックしているところに、ワーカースレッドがUIスレッドのウィンドウへSendMessageを送ると、互いに相手の完了を待ち合ってデッドロックになるワーカースレッドUIスレッドワーカースレッドUIスレッドメッセージを処理できない(待機中)SendMessageから戻れない互いに待ち合いデッドロックワーカーの完了を待機(ブロック)SendMessage(処理完了まで戻らない)

図2: ワーカーへの分離だけでは不十分。UIとワーカーが互いの処理完了を待つと、デッドロックになる。

HWND_BROADCAST への送信も注意が必要です。応答しないウィンドウが1つあるだけで、送信側が巻き込まれます。待ち続けられない場面では、タイムアウトを設ける SendMessageTimeout や、処理完了を待たない PostMessage を検討します。8

4.4 COMのSTAが止まり、他スレッドも巻き込まれる

STAオブジェクトへの他スレッドからの呼び出しは、ウィンドウメッセージとして配送されます。そのため、UIスレッド(STA)が塞がると、そこへのCOM呼び出しもブロックします。UIだけでなく、呼び出し側まで待ち状態になる構造です。

アパートメントとメッセージ処理の関係は、COM STA/MTAの記事で解説しています。

4.5 「1回は一瞬」を100回続ける

1回50msの同期処理でも、100回続ければ5秒です。個々の関数だけを測って短いと判断せず、UIスレッドがメッセージループに戻るまでの合計時間で考えます。

体感の「もっさり」は100ms程度から始まります。応答なし判定の5秒を目標にせず、UIスレッドを塞いでよい時間はミリ秒単位、と考えて設計します。

5. 固まらない設計:仕事の実行と画面の更新を分ける

5.1 計算・同期APIと、非同期I/Oを分ける

対策の原則は、時間のかかる仕事をUIスレッドから追い出すことです。ただし、すべてを同じ方法で移すわけではありません。

仕事の種類 C#での基本的な扱い UIスレッドの役割
CPU負荷の高い計算 Task.Run でワーカースレッドへ移す 完了を非同期に待ち、結果を表示する
同期APIしかない処理 ワーカースレッドへ分離する 同期の完了待ちでUIを塞がない
非同期APIがあるI/O GetStringAsync などを await する I/O待ちの間はメッセージ処理へ戻る
入力・描画・進捗・キャンセル UIスレッドで担当する 長い計算や同期I/Oを混ぜない

非同期I/Oでは、待機のためだけにスレッドを占有する必要がありません。元のコード例も、計算には Task.Run、HTTP通信にはネイティブな非同期APIを使い分けています。3

5.2 C#ではasync/awaitでUIへ戻して更新する

次は、WinFormsのクリックハンドラーで、重い仕事とUI更新を分ける例です。UIイベントハンドラーから開始し、UIの実行コンテキストを保つこの例では、await の継続でUIスレッドに戻ってコントロールを更新できます。3

private async void RunButton_Click(object sender, EventArgs e)
{
    runButton.Enabled = false;
    try
    {
        // CPU負荷の高い処理・同期しかないAPIはTask.Runでワーカーへ
        var result = await Task.Run(() => HeavyCalculation(input));

        // I/OはネイティブにasyncなAPIを使う(スレッドも消費しない)
        var data = await httpClient.GetStringAsync(url);

        // awaitの後はUIスレッドに戻っているので、コントロールを直接触れる
        resultLabel.Text = result + data.Length.ToString();
    }
    catch (Exception ex)
    {
        // async voidハンドラーから例外を漏らすとアプリが落ちる。ここで受け止める
        MessageBox.Show($"処理に失敗しました: {ex.Message}");
    }
    finally
    {
        runButton.Enabled = true;
    }
}

読み取るポイントは、次の4つです。

箇所 意図
await で完了を待つ 待機が生じたときにUIスレッドをメッセージループへ戻す
await の後で画面を更新する ワーカーからコントロールを直接触らない
実行中のボタン無効化 同じ処理が繰り返し開始されるのを防ぐ
catch と finally async void ハンドラーから例外を漏らさず、ボタンの状態を戻す

これは役割分担を示すコード例で、HeavyCalculation などの実装や、フォーム終了時の扱い、進捗・キャンセル処理は省略しています。長時間処理に必要な進捗と中断は5.4のように別途組み込みます。

WinFormsのコントロールやWPFの要素は、作成したスレッドから扱います。ワーカーから直接触ると例外や不定動作につながります。明示的にUIスレッドへ戻す場合は、WinFormsの Control.Invoke / BeginInvoke、WPFの Dispatcher.InvokeAsync を使います。3

5.3 Win32ではPostMessageで完了を通知する

Win32ネイティブでも役割分担は同じです。ワーカーに仕事を渡し、完了したら PostMessage で自作の完了メッセージを送り、UIスレッドのウィンドウプロシージャで画面を更新します。PostMessage はキューに積んだら戻るため、ワーカーはUIの処理完了を待ちません。7

固まらないアプリの役割分担UIスレッドは入力受付・進捗表示・キャンセル受付だけを担当し、重い処理はワーカースレッドが実行して、完了をPostMessageやawaitの継続でUIスレッドに返す仕事を渡すPostMessage / awaitの継続UIスレッド:入力・進捗・キャンセルワーカースレッド:重い処理UIスレッドでの同期I/O・長い計算は禁止

図3: 重い計算や同期処理はワーカーへ渡し、画面の更新はUIスレッドに戻す。非同期I/Oは5.1のとおり、非同期APIを使って待つ。

ワーカーの待ち合わせそのものは、条件変数の記事で扱った規律に従って書きます。UIスレッドがワーカーを同期的に待ち直さないことも重要です。

5.4 進捗とキャンセルを最初から設計する

画面が固まらなくても、長時間何も変わらなければ、ユーザーには動いているのか分かりません。そこで、進捗表示とキャンセル受付も処理の一部として設計します。

連絡の向き 使う仕組み 役割
ワーカー → UI IProgress<T> 進み具合を画面に伝える
UI → ワーカー CancellationToken 中止の要求を伝える

ワーカーは区切りのよい箇所で中断し、後始末を行います。進捗と中止の手段があれば、ユーザーが強制終了に頼る場面を減らせます。強制終了は、データ破損につながることもあるためです。

6. 回避策に見える2つの方法と、その限界

6.1 DoEventsは、処理の途中へ別のイベントを招き入れる

重い処理の間に Application.DoEvents() や PeekMessage ループを差し込めば、メッセージを処理して応答なし表示を回避できます。しかし、元の処理がまだ終わっていない途中で、別のイベントハンドラーが実行されるようになります。これが再入です。

たとえば、データを加工している途中で DoEvents を呼び、溜まっていたボタンの再クリックが処理される。そのハンドラーが同じデータを書き換えたあと、元の処理が再開する。この順番では、元の処理が想定していた状態は既に失われています。

再入するのはボタンだけではありません。フォームを閉じる操作やタイマーも入ってきます。処理中のデータが壊れる、閉じたフォームに触って例外になる、といったバグはタイミング依存で再現しにくく、応答なしより調査が難しくなりがちです。

原則はポンプを手で回すのではなく、仕事を分離することです。手動のメッセージ処理は、モーダルな進捗ダイアログのような限定された構造の中だけにとどめます。通常の長時間処理では、5章の分離と再入防止を使います。

6.2 ゴーストウィンドウを無効にしても、操作は戻らない

DisableProcessWindowsGhosting は、呼び出したプロセスのゴーストウィンドウへの差し替えを無効にするAPIです。無効化はプロセスの生存期間中続きます。4

これは、キオスク端末などで「OSが代わりの操作可能なウィンドウを出すこと」を避けたい特殊な用途のためのものです。メッセージ処理が止まっている事実は変わらず、ユーザーはゴーストウィンドウによる移動や終了の手段も失います。一般のアプリの応答なし対策として使うものではありません。

方法 変わること 残る問題
DoEvents などで手動ポンプを回す 処理の途中でも別のメッセージを処理する 任意のイベントが再入し、状態が壊れ得る
DisableProcessWindowsGhosting OSによる代替ウィンドウへの差し替えを止める UIスレッドは塞がったまま
時間のかかる仕事をUIから分離する UIスレッドが入力・描画へ戻れるようにする 進捗・キャンセル・再入防止も設計する

7. 調査手順:閉じる前に、固まっている瞬間を残す

7.1 最初にダンプを取得する

「たまに固まる」の調査で最も価値があるのは、固まっているその瞬間のスレッド状態です。終了・再起動してしまうと、その状態は失われます。

タスクマネージャーの「詳細」タブで対象プロセスを右クリックし、「ダンプ ファイルの作成」を選びます。全スレッドのスタックを含む完全ダンプを取得できます。問い合わせを受ける側にも、「固まったら閉じる前にダンプを取る」という手順を共有しておきます。

収集の仕組みづくりは、クラッシュダンプ収集の記事を参照してください。

7.2 固まったウィンドウの所有スレッドを見る

WinDbgでダンプを開き、UIスレッドのスタックを確認します。メッセージループを回すスレッドは通常スレッド0ですが、複数UIスレッドのアプリもあるため、番号だけで決めず、固まったウィンドウの所有スレッドを見ます。

スタックに見えるもの 次に調べる対象
ReadFile やネットワークAPIでの待機 ファイルアクセス、同期I/O、ネットワークの応答待ち
WaitFor… 系での待機 ロックや同期対象。何の完了を待ち、その相手は何をしているか
SendMessage の内部での待機 宛先スレッドがメッセージを処理できるか。互いに待っていないか

UIスレッドのスタックには、何を待って止まっているかが現れます。デッドロックが疑われる場合は、片方だけでなく両スレッドの待ち先を突き合わせます。具体的な読み方はWinDbg入門記事で解説しています。

応答なし調査の基本手順固まっている瞬間にダンプを取り、UIスレッドのスタックを見て、同期I/O・ロック待ち・スレッド間SendMessageのどれで止まっているかを特定し、該当する設計修正につなげる固まっている瞬間ダンプを取る(閉じる前に)UIスレッドのスタックを見る同期I/O・ネットワーク待ちロック待ちスレッド間SendMessage該当箇所をワーカーへ分離

図4: 固まっている瞬間のダンプからUIスレッドの待ち先を追う。I/Oなら非同期化・分離、ロックやSendMessageなら待ち合わせの構造まで確認する。

7.3 ライブ確認と時系列の分析を使い分ける

ダンプに加え、動いているプロセスをその場で見る方法と、時間の流れを記録する方法があります。

調べたいこと 方法
固まっている瞬間を保存し、後から調べる ダンプを取得してWinDbgで解析する
生きているプロセスのスレッドとスタックをその場で見る Process Explorerを使う
定常的なもっさりや、UIスレッドの待ちを時系列で追う WPRでトレースを取り、WPAで分析する

時系列の取り方はWPR/WPA実践で扱っています。

7.4 症状から候補を絞り、証拠で確かめる

再現条件も、調査の入口になります。ただし、症状だけで原因を決めず、ダンプやトレースと突き合わせます。

症状 まず疑うもの 確認する場所
特定の操作で必ず固まる そのハンドラー内の同期I/Oなど 操作に対応する処理とUIスレッドのスタック
まれに固まり、操作との相関がない ロック順序、スレッド間 SendMessage のデッドロック 待ち合っている両スレッドのスタック
特定環境でだけ固まる ネットワークドライブ、プロキシ、ウイルス対策ソフトなどによる待ちやタイムアウト 待機中のAPIと、その環境での応答時間

8. まとめ

「応答なし」は、ウィンドウのメッセージ処理が止まったことをWindowsが判定し、代わりの画面を出す仕組みです。判定の単位はウィンドウと所有GUIスレッドであり、プロセス全体ではありません。5秒という内部タイムアウトは将来変更され得る値で、快適なUIの応答時間とは区別します。12

直す対象は表示ではなく、UIスレッドを長く占有している計算や待機です。CPU処理・同期しかないAPIはワーカーへ、非同期I/Oは非同期APIへ分け、画面の更新はUIスレッドへ戻します。進捗・キャンセル・再入防止もセットで設計します。DoEvents による回避やゴーストウィンドウの無効化は、その代わりにはなりません。

原因が分からないときは、終了する前にダンプを取り、固まったウィンドウの所有スレッドが何を待っているかを見ることから始めます。「壊れたように見える」を「UIスレッドがメッセージ処理に戻れない」に置き換えると、調べる場所と設計の直し方がつながります。

関連記事

関連する相談領域

合同会社小村ソフトでは、「たまに固まる」「応答なしになる」業務アプリの原因調査(ダンプ解析・トレース分析)、同期処理だらけのレガシーUIコードのasync/await化・ワーカースレッド分離の改修、フリーズしないUI設計のレビューを扱っています。再現手順が不明な段階からでも、証拠の取り方の設計からお手伝いできます。

参考リンク

  1. Microsoft Learn, IsHungAppWindow function (winuser.h). アプリが「入力を待っておらず、起動処理中でもなく、内部タイムアウトである5秒の間PeekMessageを呼んでいない」場合に応答なしとみなされるという判定基準、この5秒という基準は変更されうること、ゴーストウィンドウに対しては常にTRUEが返ることについて。 ↩ ↩2 ↩3 ↩4

  2. Microsoft Learn, GetMessage function (winuser.h). トップレベルウィンドウが数秒間メッセージに応答しなくなると、システムがそのウィンドウを応答なしとみなし、同じZオーダー・位置・サイズ・外観を持つゴーストウィンドウに差し替えること、ユーザーは移動・サイズ変更・クローズだけができること、デバッガー接続中はゴーストウィンドウが生成されないことについて。 ↩ ↩2 ↩3 ↩4 ↩5

  3. Microsoft Learn, How to make thread-safe calls to controls (Windows Forms). WinFormsのコントロールが作成されたスレッド以外から安全に触れないこと、他スレッドからの更新にはInvoke/BeginInvokeを使うこと、async/awaitやBackgroundWorkerを使った安全な非同期パターンについて。 ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, DisableProcessWindowsGhosting function (winuser.h). 呼び出したGUIプロセスについて、応答なしウィンドウを最小化・移動・クローズ可能にするゴーストウィンドウ機能を無効化できること、無効化はプロセスの生存期間中続くことについて。 ↩ ↩2

  5. Microsoft Learn, About Messages and Message Queues. Windowsアプリがイベント駆動であり、ウィンドウプロシージャがメッセージを処理する構造、キュー経由のメッセージと直接送信されるメッセージの区別、応答なしウィンドウのゴーストウィンドウへの差し替え、スレッド間でメッセージを送り合うことによるデッドロックの節について。 ↩ ↩2 ↩3

  6. Microsoft Learn, Using Messages and Message Queues. GetMessage・TranslateMessage・DispatchMessageによる標準的なメッセージループの実装例と、メッセージキューの調査方法について。 ↩

  7. Microsoft Learn, SendMessage function (winuser.h). SendMessageが指定ウィンドウのウィンドウプロシージャを呼び出し、処理が完了するまで戻らないこと、別スレッドのウィンドウへの送信では相手スレッドがメッセージを処理するまで待たされること、応答を待たずキューに積むPostMessageとの違いについて。 ↩ ↩2 ↩3 ↩4

  8. Microsoft Learn, SendMessageTimeout function (winuser.h). タイムアウト付きでメッセージを送信できること、応答しないウィンドウ(ハング判定されたウィンドウ)に対して待たずに戻るフラグ(SMTO_ABORTIFHUNG)が用意されていることについて。 ↩

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

「応答なし」はどういう条件で表示されるのですか?
OSは、ウィンドウを持つアプリが「入力を待っておらず、起動処理中でもなく、5秒間メッセージの取り出し(PeekMessage)を行っていない」とき、そのウィンドウを応答なしと判定します。判定されたトップレベルウィンドウは非表示にされ、同じ位置・サイズ・外観の「ゴーストウィンドウ」に差し替えられます。タイトルバーの「(応答なし)」表示や白くかすむ見た目はこのゴーストウィンドウのもので、移動・最小化・閉じる操作だけができます。つまり「応答なし」はアプリ自身の表示ではなく、OSが肩代わりして出している画面です。
処理中に「応答なし」と表示されないようにする設定はありますか?
DisableProcessWindowsGhostingを呼ぶと、そのプロセスに対するゴーストウィンドウへの差し替えを無効化できます。ただしこれは「固まっていることをユーザーから見えにくくする」だけで、ウィンドウが操作に反応しない事実は変わらず、ユーザーには移動も閉じることもできない完全な固まり方として見えます。根本対策は表示の抑止ではなく、UIスレッドを5秒どころか0.1秒単位でも塞がないよう、重い処理をワーカースレッドに移すことです。なおデバッガー接続中はOSがゴーストウィンドウを生成しないため、「デバッグ中は応答なしにならない」ように見える点にも注意してください。
DoEvents(メッセージポンプの手動駆動)で応答なしを回避してもよいですか?
推奨しません。重い処理の途中でDoEventsやPeekMessageループを回すと、応答なし判定は回避できますが、処理の途中でボタンの再クリック・ウィンドウを閉じる操作・タイマーなど任意のイベントハンドラーが再入してきます。処理中のデータを別のハンドラーが書き換える、閉じたはずのフォームに触って例外になる、といった再入バグはタイミング依存で再現しにくく、応答なしより厄介です。正攻法は処理そのものをTask.Runなどでワーカースレッドに移し、UIスレッドは進捗表示とキャンセル受付だけを担当する形にすることです。
ワーカースレッドから画面(コントロール)を更新するにはどうすればよいですか?
WinFormsのコントロールやWPFの要素は、作成したスレッド(通常はUIスレッド)からしか触れません。ワーカースレッドから直接触ると例外や不定動作になります。C#ならasync/awaitを使うのが最も簡単で、awaitの継続は呼び出し元のUIスレッドに戻ってくるため、awaitの後で普通にコントロールを更新できます。明示的に切り替える場合はWinFormsではControl.Invoke/BeginInvoke、WPFではDispatcher.InvokeAsyncを使います。Win32ネイティブでは、ワーカースレッドからPostMessageで自作の完了メッセージをUIスレッドに送り、ウィンドウプロシージャ側で画面を更新するのが定石です。
「応答なし」になっているアプリの原因はどう調べればよいですか?
固まっている「その瞬間」に状態を取るのが重要です。まずタスクマネージャーの詳細タブから「ダンプファイルの作成」で完全ダンプを取り、WinDbgでUIスレッド(メッセージループを回しているスレッド)のスタックを見ます。同期I/O・ネットワーク待ち・ロック待ち・SendMessageによる他スレッド待ちのどれで止まっているかはスタックにそのまま現れます。生きた状態で見るならProcess Explorerのスレッド一覧とスタック表示、時系列で追うならWPRでのトレース採取が有効です。当サイトのWinDbg入門・Process Explorer実践・WPR/WPA実践の各記事も参考にしてください。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る