ニートが学ぶ非同期処理 その1

はいどーもこんにちは無職です。
記念すべき第一回目は非同期処理でよく見かけるスレッドについて勉強していきたいと思いますよ(第一回といいつつ非同期系の記事はちょっと書いてます)。
そもそもスレッドって何という話はしませんし(本読むかググったらいっぱい出てきますし)、僕の備忘録を兼ねている記事なので間違があるかもしれません。
そこのところご了承ください。

あ、ちなみに今回はasync/awaitは使いません。

シンプルなスレッド

もっともシンプルなスレッドの形と言えば、やはりThreadクラスを直接利用することでしょう。
例えばこんな感じ。

最初にThreadクラスのインスタンスを生成して、別スレッドを作成します。
引数で渡したデリゲートが別スレッドで実行されます。
ここで一番シンプルなデリゲートを渡していますが、いくつかのオーバーロードもあるようです。
その後、Startメソッドで別スレッドを開始します。
Joinは「呼び出し元のスレッド(今回の場合だとメインスレッド)のブロック」を意味します。
で、スレッドが終了したらメインスレッドに戻ってきます。

プールするスレッド

次はスレッドプールのスレッドを利用してみたいと思います。
というわけでとりあえずコードをドン。

ThreadPool.QueueUserWorkItemにWaitCallbackを登録すると、スレッドプールが自動でスレッドを割り当て、実行を開始します。
つまり先ほどのThreadを直接使う場合と違ってt.Start()などとしなくてよいのですね。
また、Threadを直接使う場合はスレッドプールを利用しません。

ご覧の通り引数も渡せています。
QueueUserWorkItemの第二引数はObject型を受け取るので、なんでも渡せます。
便利そうですね。
ただThreadと違って、スレッドの終了待ちというのは面倒な方法を取らないとできないっぽい?

delegateを使う

今度はdelegateを使って別スレッドを開始してみたいと思います。
というわけでコードをドン。

最初にActionデリゲートを生成しています。
それからBeginInvokeを使ってスレッドプールのキューにメソッドを登録します。
BeginInvokeの第一引数はActionデリゲートの引数に渡されます(要は「サン&ムーン」と表示される)。
このデリゲートの実行が終わると、次に第二引数のAsyncCallbackが呼ばれます。
第三引数にはAsyncCallbackに渡したい引数を渡しています。

ただよくわからなかったのが、上記のコードをそのまま実行すると、AsyncCallbackが実行される前に「メインスレッドだよ」が表示されました。
コメントアウトしてる部分(thread.Sleep(1);)も含めて実行すると、AsyncCallbackが実行されたあとに「メインスレッドだよ」が表示されました。

この辺はもう少し挙動をちゃんと把握しておかないとハマりそうですね……。

BackgroundWorkerを使う

今度はBackgroundWorkerを使ってスレッドプールのスレッド処理を実行してみたいと思います。
これはもともとWindowsForms用に導入されたもののようですが、別にコンソールアプリでも使えないことはないっぽいです。
というわけでコードをドン。

最初にBackgroundWorkerのインスタンスを作成しています。
起動したときに実行したい処理はDoWorkイベントハンドラに登録します。
このスレッドの処理が終了したときに実行したい処理はRunWorkerCompletedイベントハンドラに登録します。
実際にスレッドキューに追加するのはRunWorkerAsyncメソッドです。

これも先ほどのdelegateと似ていて、DoWorkイベントハンドラに登録したメソッドの実行が全て終了したらIsBusyはfalseになるっぽいです。
つまり

1.ワーカー開始
2.ビジーだよ
3.メインスレッドだよ
4.ワーカー終了

という順番になることもあるようでした。

タスクを使う

さてここからみんな大好きタスクです。
いや好きかどうかはともかく、一度くらいは名前を聞いたことがあるやつです。
とりあえずコードをドン。

上記のコードを実行すると、ほとんどの環境では「メインスレッドだよ」が表示されたあとに「タスク開始」か「ファクトリでタスク開始」が表示されると思います。
これはスレッドプールのキューに仕事を割り当てるのにオーバーヘッドがかかっているからではないかなと思います。
また、見ての通りタスクの開始方法は一つではありません。

Taskの完了を待ちたい場合もあると思います。
最近だとasync/awaitを使うことが多いと思うのですが、今回は勉強なので普通にtask.Wait();を使ってみました。
コメントアウトしている部分も実行させると、「メインスレッドだよ」が最後に表示されると思います。

別解としては
Task.WaitAll(task, task2);
みたいな感じで完了を待つこともできます。

終わりに

今回はスレッドでなんやかんやする処理の基本のキを勉強してみました。
このシリーズどんどん続けたいと思いますよ。
ほなそんな感じでまた。

フォローする