C#における非同期処理、並列処理の実現手段であるtaskクラス。使い方だけを書いてある記事は沢山あるのだけど、前提知識などがひとまとめになっている記事がなかなか見つからないので、自分の知識整理を目的に今回書いてみることにした。
事前知識
taskクラスを使用するうえで習得しておくべき知識として、delegateについて必ず理解しておく必要があります。 本記事に書こうかとも思ったのですが、思いのほか長くなってしまったので別記事にまとめました。まずはこちらをご覧ください。
Taskクラスの使い方
処理を別スレッドで実行する最も基本的な方法
まずは、Taskクラスの最も基本的な使い方を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
static void Main(string[] args) { Action del_func_inst = hoge1; Task t1 = new Task(del_func_inst); t1.Start(); hoge2(); return; } static void hoge1() { for(int i=0; i<10; i++) { Console.WriteLine("hoge1 is called. {0}\n", i); } return; } static void hoge2() { for (int i = 0; i < 10; i++) { Console.WriteLine("hoge2 is called. {0}\n", i); } return; } |
上記コードの時系列を示したのが下記の例です。
- Action Delegateを宣言
- タスクを作る(Taskのインスタンス化)
- タスクを実行する
Taskクラスに別スレッドで実行してほしい処理hoge1()
を渡すのですが、その際にAction Delegateという箱に入れて渡す必要があります。 まずは、Action Delegate del_func_inst
を定義し、hoge1をそこに登録します。
次に、Taskを作成します。このとき、最初に作成したActionを渡します。これによって、del_func_inst
(=hoge1())を持った状態のtaskを作成することができました。
この時点ではまだ処理は実行されません。
次にTask.Start()
メソッドを使って、taskの処理を実行します。この時点で、hoge1()
が別スレッドで走り始めます。
今回の例のような簡単な処理の場合は、いちいちActionを定義すると煩雑です。 その場合は、下記のようにラムダ式を使って記述し、Actionの宣言を省略することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
static void Main(string[] args) { //ラムダ式でActionの宣言を省略 Task t1 = new Task(() => { hoge1(); }); t1.Start(); hoge2(); return; } static void hoge1() { for(int i=0; i<10; i++) { Console.WriteLine("hoge1 is called. {0}\n", i); } return; } static void hoge2() { for (int i = 0; i < 10; i++) { Console.WriteLine("hoge2 is called. {0}\n", i); } return; } |
ネットの記事でよく見るのはこのラムダ式を使った書き方ですね。
Taskのインスタンス化と実行をまとめて行うTask.Run()
ここまで、Taskクラスをインスタンス化してTask.start()
で実行するという例を示しました。しかし、インスタンス化→実行という2ステップがあって少し煩雑です。そこで、これらを1つにまとめたTask.Run()
メソッドを利用します。
Task.Start()
を使うよりも、こちらの方が一般的です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
static void Main(string[] args) { //Taskのインスタンス化と実行をまとめて行う Task t1 = Task.Run(()=> { hoge1(); }); hoge2(); return; } static void hoge1() { for(int i=0; i<10; i++) { Console.WriteLine("hoge1 is called. {0}\n", i); } return; } static void hoge2() { for (int i = 0; i < 10; i++) { Console.WriteLine("hoge2 is called. {0}\n", i); } return; } |
Taskの終わりを待つ Task.Wait()
ここまで説明した例では、Taskで行われる別スレッド処理は投げっぱなしで、いつ終わるかは全くケアされていません。 プログラムによっては、別スレッドで行う作業が終わってから次の処理に移りたいということがあります。
そのときに使用するのがTask.Wait()メソッドです。
先ほどのコードを少し改造し、別スレッドで実行したhoge1が終了するのを待ってからhoge2を実行する例を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
static void Main(string[] args) { Task t1 = Task.Run(()=> { hoge1(); }); t1.Wait();//hoge1の処理が終了するまで待つ hoge2(); return; } static void hoge1() { for(int i=0; i<10; i++) { Console.WriteLine("hoge1 is called. {0}\n", i); } return; } static void hoge2() { for (int i = 0; i < 10; i++) { Console.WriteLine("hoge2 is called. {0}\n", i); } return; } |
Task.Run()とTask.Factory.Startnew()の違い
MSDN公式ドキュメントの非同期処理関連の記事を見てみると、Task.Run()よりも、Task.Factory.Startnew()が混在していることに気づいた。Qiitaなどのネットの記事を見るとTask.Run()がよく使用されているようだけど、MSDNの記事ではTask.Factory.Startnew()の方がよく使われているように思える。
このトピックについて、MSDNの記事があった。
Task.Factory.Startnew()は.NET 4, Task.Run()は.NET4.5 で追加されている。つまり、Task.Run()の方が新しい。
上記記事でも、
In this way, Task.Run can and should be used for the most common cases of simply offloading some work to be processed on the ThreadPool (what TaskScheduler.Default targets).
と言っているように、多くの場合はTask.Run()を使用することを推奨されている。
結論、どっちを使ってもいいけど、基本的にはTask.Run()を使うということでOKという。