http://1studying.blogspot.com/2017/04/c.html
の補足情報です。
補足では、
「親スレッド」→「メインスレッド」
「子スレッド」→「ワーカースレッド」
として、正しい名称で説明しています。
「Task.Wait()」と「デットロック」と「Task.Result」
リソースの待ち合いによる「デットロック」
「Task.Wait()」は「Task」の処理終了を待つ間、
「メインスレッド」の動作を止めてしまいます。
その時に、
「ワーカースレッド」側から「Invoke」で
「メインスレッド」側の「Form1」内を触ろうとしても、
「メインスレッド」側は「ワーカースレッド」の処理終了まで
動作を止めている為、「メインスレッド」の動作が再開する迄
「ワーカースレッド」も処理を止めます。
すると、
「メインスレッド」も「ワーカースレッド」も、
永遠に処理が進まなくなります。これが、「デットロック」です。
private void button1_Click(object sender, EventArgs e) { //「ワーカースレッド」で実行 var task = Task.Run(() => { //「0」~「5」迄カウントアップ for (int i = 0; i < 6; i++) { //「メインスレッド」側で処理を行ってもらうコードを記述 //「メインスレッド」側での処理が終了するまでの間は、 //「ワーカースレッド」の処理を止めて待機 this.Invoke(new Action(() => { //「label1」の表示更新 label1.Text = i.ToString(); })); //1秒待つ System.Threading.Thread.Sleep(1000); } }); //ここは「非同期」なので「Form1」側の処理は止まりません。 //↓ここで「同期処理」的になり「Form1」の処理が止まる。 //「ワーカースレッド」の処理が終わるまで、 //「メインスレッド」の処理を止めて待機 task.Wait(); //「デットロック」の為、ここまで処理が来ない。 MessageBox.Show("処理終了"); }実行すると「Form1」がハングアップしたような状態になります。
「Task.Wait()」と「Invoke」は相性が悪い。
「async/await」を使った方が良いです。
「Task.Wait()」の「非同期処理」化と「デットロック」回避…
通常は「async/await」を使った方が良いのですが、
「Task.Wait()」を残した方法だと、
「非同期」の処理を全て「タスク」の中で行う方法があります。
private void button1_Click(object sender, EventArgs e) { //「タスク」内の処理終了を待たずに、次の行へ処理が移ります。「非同期」 var task1 = Task.Run(() => { //「0」~「5」迄カウントアップ for (int i = 0; i < 6; i++) { //「メインスレッド」側で処理を行ってもらうコードを記述 this.Invoke(new Action(() => { //「label1」の表示更新 label1.Text = i.ToString(); })); //1秒待つ System.Threading.Thread.Sleep(1000); } }); //ここは「非同期」なので「Form1」側の処理は止まりません。 //「タスク」内の処理終了を待たずに、次の行へ処理が移ります。「非同期」 var task2 = Task.Run(() => { //↓ここは「同期」処理となり「task2」内「ワーカースレッド」の処理は止まるが、 //「タスク」内の為、「Form1」自体の処理は止まらない。 task1.Wait(); MessageBox.Show("処理終了"); }); //「task1」「task2」の処理終了を待たずに、 //この行まで処理が下りてきます。 //結果「非同期」となります。「Form1」側の処理は止まりません。 }「0→1→2→3→4→5」「処理終了」
と表示されます。
「async/await」で「Task.Result」使用時の注意
「Task.Result」の処理内には「Task.Wait()」が入っています。
「Task.Result」を使う時には必ず「Task」に対して、
「await」をしておいた方が良いです。
private async void button1_Click(object sender, EventArgs e) { //「ワーカースレッド」で実行 var task = Task<string>.Run(() => { //「0」~「5」迄カウントアップ for (int i = 0; i < 6; i++) { //「メインスレッド」側で処理を行ってもらうコードを記述 this.Invoke(new Action(() => { //「label1」の表示更新 label1.Text = i.ToString(); })); //1秒待つ System.Threading.Thread.Sleep(1000); } return "リザルト"; }); //「await」なので待っている間「Form1」の処理は止まらない。「非同期」 await task; //戻り値取得 var resultStr = task.Result; ////↓まとめた書き方でも良い。(通常こちらをつかいます!) ////「await」なので戻り値を待つ間「Form1」の処理は止まらない。「非同期」 //var resultStr = await task; MessageBox.Show("処理終了:"+resultStr); }「0→1→2→3→4→5」「処理終了:リザルト」
と表示されます。
「Task.Wait()」と「例外処理」
「Task.Wait()」と「例外」
「AggregateException」でキャッチできます。
private void button1_Click(object sender, EventArgs e) { //「非同期」で実行 var task = Task.Run(() => { System.Threading.Thread.Sleep(1000); // 1秒待つ throw new Exception("例外タスク内"); //「例外」発生 }); //ここは「非同期」なので「Form1」側の処理は止まりません。 try { //↓ここで「同期的」になり「Form1」側の処理が止まる。 task.Wait(); } catch (AggregateException aex) // 「例外」キャッチ { foreach (var ex in aex.InnerExceptions) { MessageBox.Show(ex.Message); } } MessageBox.Show("処理終了"); }「例外タスク内」→「処理終了」
と表示されます。
複数の「Task」をまとめる
複数の「Task」をまとめるには…
「Task.WhenAll()」を使います。
private async void button1_Click(object sender, EventArgs e) { var tasks = new List<Task>(); // TaskをまとめるListを作成 //「0」~「5」迄カウントアップ for(int i=0; i<6; i++) { var task = MyTaskRun(i); //複数の「Task」を作成 tasks.Add(task); //TaskをListへ追加 } var allTask=Task.WhenAll(tasks);//すべてのタスクをまとめる。 //「await」なので待っている間「Form1」の処理は止まらない。「非同期」 await allTask; MessageBox.Show("処理終了"); } private Task MyTaskRun(int sec) { return Task.Run(() => { System.Threading.Thread.Sleep(sec * 1000); // sec秒待つ this.Invoke(new Action(() => { label1.Text = sec.ToString(); })); }); }「0→1→2→3→4→5」「処理終了」
と表示されます。
「Task.WhenAll()」は引数を、
「Task.WhenAll(task1,task2,task3)」のように書く事も可能です。
複数「Task」の複数「例外処理」
複数「Task」中で起きた複数「例外処理」のキャッチ
「Task.WhenAll()」でタスクをまとめてから、
「Task.Wait()」なら「AggregateException」
「await」なら「Exception」で
複数「Task」内で起きた「例外」をキャッチします。
「Task.Wait()」使用時の複数「例外処理」
それぞれの「Task」で起きた「例外」全てをキャッチする事ができます。
private void button1_Click(object sender, EventArgs e) { //「非同期」で実行 var task1 = Task.Run(() => { System.Threading.Thread.Sleep(1000); // 1秒待つ throw new Exception("例外タスク1"); }); var task2 = Task.Run(() => { System.Threading.Thread.Sleep(2000); // 2秒待つ throw new Exception("例外タスク2"); }); var task3 = Task.Run(() => { System.Threading.Thread.Sleep(3000); // 3秒待つ throw new Exception("例外タスク3"); }); //「Task」をまとめる var task = Task.WhenAll(task1, task2, task3); //ここは「非同期」なので「Form1」側の処理は止まりません。 try { //↓ここで「同期的」になり「Form1」側の処理が止まる。 task.Wait(); } catch (AggregateException aex) { foreach (var ex in aex.InnerExceptions) { MessageBox.Show(ex.Message); } } MessageBox.Show("処理終了"); }例外が「AggregateException」でキャッチされ、
「例外タスク1」→「例外タスク2」→「例外タスク3」→「処理終了」
と表示されます。
「await」使用時の複数「例外処理」
最初に「Task」で起きた「例外」のみをキャッチする事ができます。
private async void button1_Click(object sender, EventArgs e) { //「非同期」で実行 var task1 = Task.Run(() => { System.Threading.Thread.Sleep(1000); // 1秒待つ throw new Exception("例外タスク1"); }); var task2 = Task.Run(() => { System.Threading.Thread.Sleep(2000); // 2秒待つ throw new Exception("例外タスク2"); }); var task3 = Task.Run(() => { System.Threading.Thread.Sleep(3000); // 3秒待つ throw new Exception("例外タスク3"); }); try { //「Task」をまとめる //「await」なので待っている間「Form1」の処理は止まらない。「非同期」 await Task.WhenAll(task1, task2, task3); } // 「await」では「例外」キャッチは最初の1つのみとなる catch (Exception ex) { MessageBox.Show(ex.Message); } MessageBox.Show("処理終了"); }例外が「Exception」でキャッチされ、
「例外タスク1」→「処理終了」
と表示されます。
「BeginInvoke」と「EndInvoke」
「Invoke」と「BeginInvoke」の違い…
「Invoke」は「同期」(呼び出し元の処理は停止して待機)(SendMessage的)
「BeginInvoke」は「非同期」(呼び出し元の処理は継続)(PostMessage的)
「タスク」内「Invoke」。「同期」処理。
「Invoke」内の処理が終了するのを待ち、処理が終了したら次の行へ処理を移します。
//「メインスレッド」側で処理を行ってもらうコードを記述。「同期」 this.Invoke(new Action(() => { //「label1」の表示更新 label1.Text = i.ToString(); }));
「タスク」内「BeginInvoke」。「非同期」処理。
「BeginInvoke」内の処理終了を待たずに、次の行へ処理が移ります。
//「メインスレッド」側で処理を行ってもらうコードを記述。「非同期」 this.BeginInvoke(new Action(() => { //「label1」の表示更新 label1.Text = i.ToString(); }));
その他タスク的「BeginInvoke」「EndInvoke」…
タスク的な「BeginInvoke」と「EndInvoke」の使い方は、
現在「Task」クラスで代用する為、ほぼ使われていません。
概要程度でOK。
「BeginInvoke」のタスク的使用例は以下の形になります。
「BeginInvoke」内で「Invoke」を使う事も可能。
private void button1_Click(object sender, EventArgs e) { //「非同期処理」本体 var asyncWork = new Action(()=> { for (int i=0; i<6; i++) { //「Invoke」も使える。 this.Invoke(new Action(()=>{ label1.Text = i.ToString(); })); System.Threading.Thread.Sleep(1000); } }); //「非同期処理」終了後に処理 var endAsyncCallback = new Action<IAsyncResult>((ar) => { MessageBox.Show("非同期処理終了"); }); //「非同期処理」開始。コールバック付き。 // 処理の終了を待たずに次の行へ処理が進みます。 asyncWork.BeginInvoke(new AsyncCallback(endAsyncCallback), null); //「非同期」なので「Form1」側の処理は止まりません。 }「0→1→2→3→4→5」「非同期処理終了」
と表示されます。
「EndInvoke」を使うと「BeginInvoke」処理のコールバックを
「メインスレッド」側で待つ事ができます。
待っている間は「同期処理」となります。(「Task.Wait()」と同じ)
「ワーカースレッド」に対して「引数」と「戻り値」を付ける場合の
記述もしておきます。
private void button1_Click(object sender, EventArgs e) { //「非同期処理」本体(戻り値付き、引数付き) var asyncWork = new Func<string,string> ((addText)=> { for (int i=0; i<6; i++) { ////「Invoke」を使うと「デットロック」する為、使用しない。 //this.Invoke(new Action(()=>{ // label1.Text = i.ToString()+addText; // })); Console.WriteLine(i.ToString()+addText); System.Threading.Thread.Sleep(1000); } return "戻り値"; }); //「非同期処理」開始。引数付き。コールバック無し(null)。 // 処理の終了を待たずに次の行へ処理が進みます。 var returnAr = asyncWork.BeginInvoke("秒経過", null, null); //ここは「非同期」なので「Form1」側の処理は止まりません。 //↓ここで「同期処理」的になり「Form1」側の処理が止まる。 //「ワーカースレッド」の処理が終わるまで、 //「メインスレッド」の処理を止めて待機 //(「Task.Wait()」と同じ。「Form1」が動かなくなる。) //戻り値の取得 var resultStr=asyncWork.EndInvoke(returnAr); MessageBox.Show("処理終了:"+resultStr); }「0→1→2→3→4→5」「処理終了:戻り値」
と表示されます。
「7行目」、
コメントにしている「Invoke」の「デットロック」を避けるには、
「27行目」以降の処理を「タスク」に内包して、
「Form1」の動作を止めないようにする。
等の方法があります。
他
ライブラリを作る場合、
自作のメソッドで「Task.Run()」は使用しない。
(「ConfigureAwait(false)」についての解説)
http://qiita.com/chocolamint/items/ed4999cccf011653cb78
http://qwerty2501.hatenablog.com/entry/2014/04/24/235849
0 件のコメント:
コメントを投稿