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 件のコメント:
コメントを投稿