ボタンとその他のコントロールを備えたダイアログボックスがあります。
そのボタンが押されると、ワーカー スレッドが生成されます。
議論を簡単にするために、スレッド関数は時間のかかる仕事をしているとだけ言っておきましょう。
ボタンがクリックされるたびに、新しいスレッドが生成され、その処理が実行されます。
ワーカー スレッドがジョブを実行している間、ダイアログ ボックスがブロックされないようにする必要があります。これは、ユーザーがダイアログ ボックスを最小化したり、他のコントロールをクリックしたりできる必要があるためです。
ウィキペディアで、非ブロッキング スレッド同期を指すロックフリー アルゴリズムという用語を見つけました。
これが、ノンブロッキングスレッド同期に興味がある理由です。これにより、必要な動作が保証されると思います。
私はマルチスレッドを初めて使用しますが、ここでいくつかの記事/回答された質問を見つけ、それに関する Microsoft のドキュメントを読みました。
これらのほとんどは、次のアルゴリズムを使用します。
1.
メイン スレッドは揮発性変数 (通常は int または bool ) を宣言し、それをワーカー スレッドに渡します。ワーカー スレッドはループ内で変数をチェックし、変数が終了を示すように設定されていない場合は、作業を続行します。終了する必要がある場合、親スレッドは変数を設定します。
2.
Microsoft の Web サイトには、同期に関する膨大なドキュメントもあります。そこでは、ミューテックス、クリティカル セクション、インターロックなどを発見しました。
これらの問題は、ワーカー スレッドが終了するまで、親スレッド (通常は WaitForSingleObject または WaitForMultipleObjects API を使用) を常にブロックすることです。
また、ここで検索すると、最近の質問 (ダイアログ ボックスの閉じるボタンがクリックされたときにスレッドを適切に中止する) が見つかりました。
私の問題を解決するために、StackOverflow のルールを尊重するために、この質問を複数の質問に分割し、別々に投稿します。
したがって、今のところ私の最初の質問は次のとおりです。
非ブロッキング スレッド同期を実現するには、どの API/アルゴリズムを使用する必要がありますか?
また、可能であれば、チュートリアルやコード例へのリンクをいただければ幸いです。Google には不運だったからです (また、開発者はコード/疑似コードを通じて最もよく学習します)。
これらのコード スニペットが有用であることが判明した場合に備えて、これらのコード スニペットを使用した最初の試みを以下に示します。
// thread function
DWORD WINAPI MyThread()
{
int result = 0;
// do something
if( /** everything is OK **/ )
return result;
else
{
result = 1;
return result;
}
}
ダイアログボックスは次のようになります。
// button clicked handler
case IDC_BUTTON1:
{
// create thread
DWORD tID;
HANDLE th = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)MyThread ,
NULL , 0 , &tID );
if( !th )
EndDialog( hWnd, IDCANCEL );
CloseHandle( th );
}
break;
case IDCANCEL:
EndDialog( hWnd, IDCANCEL );
break;
この時点で、プログラムを実行し、ダイアログ ボックスをアクティブにしてボタンをクリックすると、ワーカー スレッドが生成され、終了するまで待つとうまくいきます。
それでも、ダイアログボックスを早めに閉じると、問題の性質はここで見つけた質問と同じになります (IDCANCEL ハンドラーの WaitForMultipleObjects で部分的に修正できると思いますが、別の投稿に残しておきます)。
編集#1:
進行状況と直面した問題を反映するために、新しいコードを投稿しました。
重要な注意点:
複数のスレッドを起動する代わりに、一度押されたスレッドを開始するボタンを非表示にしました。
これは、ユーザーが 2 番目のスレッドをアクティブにする前に、最初のスレッドが終了するのを待たなければならないことを意味します。
これは、デバッグを容易にするために行われました。
スレッドが正常に終了したか、エラーが発生したかを示すカスタム メッセージを定義しました。
#define WM_THREAD_OK ( WM_APP + 1 )
#define WM_THREAD_ERROR ( WM_APP + 2 )
スレッドに渡されるデータ構造を追加して、ダイアログと通信できるようにし、適切に中止できるようにします
struct Data
{
HWND hwnd;
bool bContinue;
};
メッセージをダイアログ ボックスに送信するようにスレッド関数を作り直して、ダイアログにエラーや適切な終了を通知できるようにしました。
新しいコード (これは、書籍 Charles Petzold-Programming Windows 5th ed. の例に基づいています):
// thread function
DWORD WINAPI MyThread( LPVOID lpvoid )
{
HRESULT hr;
volatile Data *data = ( Data* )lpvoid;
try
{
for( int i = 0; i < 10 && data->bContinue; i++ )
{
hr = // do something
if( FAILED(hr) )
throw _com_error(hr);
}
// once you leave loop, check if thread was aborted, or all was well
if( ! ( data->bContinue ) )
{
// thread was aborted, do cleanup and exit
// cleanup
return 0;
}
// if all went well, do cleanup, and "say" so to the dialog box
// do cleanup here
SendMessage( data->hwnd, WM_THREAD_OK, 0, 0 );
return 0; // exit gracefully
}
catch( _com_error & )
{
// do cleanup
SendMessage( data->hwnd, WM_THREAD_ERROR, 0, 0 );
return 1;
}
}
ダイアログ ボックスの新しいコードを次に示します (注: ダイアログ ボックスは現在モードレスです)。
static Data data;
HANDLE th = NULL;
// button clicked handler
case IDC_BUTTON1:
{
// create thread
DWORD tID;
th = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)MyThread ,
(LPVOID)&data, 0 , &tID );
if( !th )
DestroyWindow( hwnd );
// hide the button which activates thread
ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_HIDE );
}
break;
case WM_THREAD_OK:
if( th )
CloseHandle( th );
ShowWindow( GetDlgItem( hwnd, IDC_BUTTON1 ), SW_SHOW );
break;
case WM_THREAD_ERROR:
if( th )
CloseHandle( threadHandle );
MessageBox( hwnd, L"Error", L"Error", MB_ICONERROR );
break;
case IDCANCEL:
data.bContinue = false; // set thread abortion flag
if( th )
{
// after I comment out below statement, thread aborts properly
// WaitForSingleObject( th, INFINITE );
CloseHandle( th );
}
DestroyWindow( hwnd ); // dialog box is now modeless one!
break;
私の質問は: このコードに間違いはありますか? 改善できますか?
ありがとうございました。