最初に…
マルチスレッド(非同期処理)は
主にバックエンド処理を行う間、
フロントエンドUIの処理をフリーズさせない為に使います。
例えば、「Form」上の「ボタン」を押すと
なんらかの(バックエンド)処理が行われるソフトを作るとします。
その処理が行われている間、「Form」(フロントエンド)は
操作を受け付けなくなってしまいます。
(「Form」の移動等も出来ない。表示の更新も行われない。)
これを解決できるのがマルチスレッド(非同期処理)。
複数のスレッド処理を平行して行う事ができる為、
「Form」から他のスレッド処理を実行中に
「Form」自身の操作が可能。
「Form」自身の表示も正しく更新されます。
マルチスレッド(非同期処理)は
「Task」(lock)(Invoke)と
「async/await」(SemaphoreSlim)と
「例外処理」が使えればとりあえずOKみたいです。
使い方と周辺知識を自分なりにまとめてみました。
「Task.Wait()」については補足の方に書きました。
C# マルチスレッド、非同期の補足
http://1studying.blogspot.com/2017/04/c_22.html
に補足情報が書いてあります。
準備
「新しいプロジェクトを作成」
↓
「Windowsフォームアプリケーション」
↓
「Form1」へ
「ツールボックス」から
「Label」と「Button」を配置
↓

ボタンをダブルクリックして、
ボタンのクリックイベントを作成しておきます。
1 2 3 4 | private void button1_Click( object sender, EventArgs e)
{
}
|
やりたい事
「button1」を押すと、
「label1」の表示が「0」〜「5」迄1秒毎にカウントアップ。
その間、「Form1」の動作が止まらないように処理させたいのです。
「Form1」の動作を止めない為には、
「label1」を「5」迄カウントアップ表示させるプログラムを
「子スレッド」で処理させる必要があります。
この時、「子スレッド」の処理が終わるまでの間、
「Form1」側の動作を止めて待つのが「同期処理」
「Form1」側の動作は止めずに待つのが「非同期処理」です。
「delegate(匿名メソッド、無名メソッド、関数)」と
「定義済みデリゲート」の「Action」「Func」型を使います。
これが分からない場合はリンク先を参照しておいて下さい。
http://1studying.blogspot.jp/2017/04/c-delegateactionfunc.html
ここでは説明上、「Task」内の処理を「子スレッド」とします。
本来は「親スレッド」を「メインスレッド」、
「子スレッド」を「ワーカースレッド」と言うのが正しいです。
(「Task」を使う側は気にする必要はない事なのですが、
「Task」は裏側で「Thread」や「ThreadPool」という処理を
状況により使い分けて作業を行っています。
その為、裏で1つのスレッドを複数「Task」で使い回す事があります。)
「Task」と「Invoke」
「Task」について…
「子スレッド」で処理を行う為に「Task」を使用します。
3つの使い方がありますが、
通常は「①」しか使わないので「①」だけ覚えておけばOKです。
① var task1 = Task.Run( デリゲート );
② var task2 = Task.Factory.StartNew( デリゲート );
③ var task3 = Task( デリゲート ); task3.Start();
|
普段「①」の「デリゲート」の部分に「子スレッド」で処理させたい内容を書きます。
もし長時間の「子スレッド」実行が必要な場合は、
オプションの指定が可能な「②」を使う事もありますがほぼ使いません。
(「③」はStart()を行うまで「子スレッド」が実行されないタスクです。
「②Factory.StartNew」は「①Ran」と同じ物と思って差し支えありません。
①と②は「子スレッド」が即時に実行されます。
「.NET4.0」時代まで「②」が使われていたが、「.NET4.5」になり、
よりシンプルな「①」の方法が追加されました。)
「Invoke」について…
「インボーク」は「呼び出し」と言う意味です。
「Form1」から「Task」により、「子スレッド」が実行されたとします。
この「子スレッド」から「親スレッドのForm1」のデータを書き換える際、
「Invoke」が使われます。
(複数のスレッド間ではデータのやりとりが制限されている為、
この方法が使われます。「スレッドセーフな呼び出し」等と呼ばれます。)
「Invoke」は「子スレッド」内から
「親スレッドのForm1」に対して、処理を間借りさせてもらい、
「親スレッドのForm1」側の内部でコードを実行させる「メソッド」です。
(「親スレッドのForm1」で実行させたいコードは、
「子スレッド」内の「Invoke」の引数として、デリゲートを使い記述します。)
「Invoke」は「Form」配下の全てのコントロールで使用できます。
(「System.Windows.Formes名前空間」Controlクラスが
フォーム自身と配置される全てのコントロール(アイテム)に継承されている為です。
これにより、
「System.Windows.Formes.Control.Invoke(Delegate)」
の「メソッド」が継承され使用可能となる。)
処理の例…
これらを踏まえた処理が以下の形となります。
TaskとInvokeを使用した処理例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private void button1_Click( object sender, EventArgs e)
{
var task = Task.Run(() =>
{
for ( int i=0; i<6; i++)
{
this .Invoke( new Action(()=> {
label1.Text = i.ToString();
}));
System.Threading.Thread.Sleep(1000);
}
});
}
|
「button1」をクリックすると「子スレッド」側から、
「0→1→2→3→4→5」と「親スレッドForm1」のラベル表示を更新します。
ラベル表示更新の最中に「Form1」側の動作は止まりません。
つまり「TaskとInvoke」を使用した「非同期処理」となっています。
「Invoke」の戻り値について…
「Invoke」の戻り値はobject型です。
もし上記処理で戻り値を得たい場合は「Invoke」部分の記述が以下の形になります。
Invokeで戻り値を得たい場合var returnObj= this .Invoke( new Func< object >(() => {
label1.Text = i.ToString();
return ( object )12341;
}));
|
「Invoke」は「子スレッド内で親スレッドの処理」を行います。
つまり「Invoke」で「親スレッド」の処理を行った際の戻り値を使用すれば
「子スレッド側」から「親スレッド(Form1)」の情報、
例えば「label1」のプロパティの値などが取得可能です。
「Task」内に処理を全て書く必要性について…
この後説明する「async/await」の所でこの事が
とても重要になります。
上記「TaskとInvokeを使用した処理例」のコードを再確認してみて下さい。
「Task」内の処理(ラベルのカウントアップ処理)が「子スレッド」側で実行中の間、
「親スレッドのForm1」側は「Task」が記述された次の行へ、
すぐに処理を移してしまいます。
「Task」内の処理終了を待ってはくれません。
「非同期処理」にしたいのは
「子スレッド」側で「Task」内の処理(カウントアップ処理)を行っている間、
「親スレッド」側の「Form1」の動作を止めたくないからでしたね。
その為には、
「子スレッド」で行う予定の全処理を「Task」内にまとめて記述する必要があるのです。
ただし、後述する「async/await」を使うと、
「Task」内に全ての処理をまとめる必要がなくなります。
「async/await」はとても便利な機能です。
「Task」と「invokerequired」
「InvokeRequired」について…
「InvokeRequired」プロパティは
自分は現在「子スレッド」側として実行中なのか?を教えてくれます。
例えば、
自分が「親スレッド(Form1と同スレッド)」であれば「Invoke」は必要無いので
直接「Label1」プロパティなどを操作すれば良いです。
しかし、
自分が「子スレッド」の場合「Invoke」を使用して(介して)
「親スレッド(Form1)」内の「Label1」プロパティなどを操作する必要があります。
つまり「InvokeRequired」プロパティは
「Form1」内コントロールの操作に「Invoke」が必要かどうかを判別する為のものです。
「Invoke」が必要な状態であればtrueを返します。
InvokeRequiredを使用した処理例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 34 35 36 37 38 | private void button1_Click( object sender, EventArgs e)
{
var task = Task.Run(() =>
{
for ( int i=0; i<6; i++)
{
SetLabelStr(i.ToString());
System.Threading.Thread.Sleep(1000);
}
});
}
private void SetLabelStr( string text)
{
if ( this .label1.InvokeRequired)
{
this .Invoke( new Action(() => {
label1.Text = text;
}));
return ;
}
else
{
this .label1.Text = text;
}
}
|
「SetLabelStr」メソッド内で「InvokeRequired」を使用して、
「lavel1」への文字列表示更新処理を振り分ける形にする事で、
「SetLabelStr」メソッドがクラス内のどこからでも使える形となります。
自分が「Task」内かを意識せずに「SetLabelStr」メソッドが使えることで
扱いやすいメソッドと言えます。
「Task」と「lock」
「lock」について…
上述の「InvokeRequiredを使用した処理例」のコードを実行して、
「button1」をゆっくり2回クリックすると、
「0→1→2→3→4→5」「0→1→2→3→4→5」
と「
直列的」に処理されて表示されるのではなく、
「0→1→
0→2→
1→3→
2→4→
3→5→
4→
5」
と表示されます。
これは、2回のクリックにより実行される2つの「子スレッド」が、
「
並列的」に処理される為です。
1回目のクリックで「子スレッド」が実行、動作中の間は、
2回目以降のクリックによる「子スレッド」の動作を順番待ちさせておくのが
「lock」です。
(動作中の「子スレッド」処理が終了すると、
順番待ちしている他の「子スレッド」が順番に動作中になります。)
これにより「
直列的」に処理されているように動作します。
lockを使用した処理例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 34 35 36 37 38 39 40 41 42 43 | object lockObj = new object ();
private void button1_Click( object sender, EventArgs e)
{
var task = Task.Run(() =>
{
lock (lockObj)
{
for ( int i=0; i<6; i++)
{
SetLabelStr(i.ToString());
System.Threading.Thread.Sleep(1000);
}
}
});
}
private void SetLabelStr( string text)
{
if ( this .label1.InvokeRequired)
{
this .Invoke( new Action(() => {
label1.Text = text;
}));
return ;
}
else
{
this .label1.Text = text;
}
}
|
これで「button1」をゆっくり2回クリックした場合、
「0→1→2→3→4→5」「0→1→2→3→4→5」
という「
直列的」表示になります。
「lock」を使ってはいけない場面…
「lock」内に「await」は使えません。
「await」内に「lock」も使えません。
「await」中のロックは「semaphoreSlim」を使います。
「await」と「semaphoreSlim」については後述します。
「async/await」
「async/await」について…前説
「async/await」は「Task」をとても簡単に書く方法です。
普通だと「親スレッドのForm1」のコードの中で
「子スレッド」が「Task」内の処理を行っている間、
「親スレッドのForm1」側は「Task」が記述された次の行へ、
すぐに処理を移してしまいます。
「Task」内の処理終了を待ってはくれません。
「await」を使うと、
「親スレッドのForm1」の動作自体は止めないまま、
「Task」内の処理が終了するまで
「await」が記述された次の行への処理を待機させる事ができます。
「await」は裏側で以下のような動作をしています。
1 2 3 | 非同期で「Task」を使用して「子スレッド」を呼ぶ。
この時の「Task」の行位置を記録し、そのまま「親スレッドのForm1」自体の通常動作処理へ移行。
「Task」内の処理が終了したら「子スレッド」から、記録した行位置まで制御を戻し処理を再開。
|
「親スレッド」の動作を止めないで、
「子スレッド」からのコールバックを待つ。
みたいな動作になります。
「await」を使う事により、
前述の「InvokeRequiredを使用した処理例」コード内の
「SetLabelStr」メソッドのような書き方が必要なくなります。
(前述ではコントロールを「親スレッド」と「子スレッド」から
同じようにアクセスする為には
「SetLabelStr」メソッドのような書き方が必要だった)
コードの記述内で、
「async」修飾子が付いたメソッドを「非同期メソッド」と呼びます。
これは、
「async」付きメソッド内配下には
非同期処理を使用した「await」を
1つ以上使用していますよ。
という「印」となるものです。
「async/await」について…実践
「非同期メソッド」の処理例は以下の形になります。
「async/await」使用例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private async void button1_Click( object sender, EventArgs e)
{
for ( int i = 0; i < 6; i++)
{
label1.Text = i.ToString();
await Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
});
}
MessageBox.Show( "処理終了" );
}
|
「button1」をクリックすると、
「0→1→2→3→4→5」「処理終了」
の順で表示されます。
「await」を使えば
「Form1」の動作を止めてしまいそうな処理が重そうな部分だけを
部分的に「Task(子スレッド)」として実行させる事が出来ます。
「await」の「Task(子スレッド)」で処理が行われているあいだ、
「Form1」の動作は止まらない状態で、
コード次行処理への進行のみが止まった状態となります。
「Task(子スレッド)」の処理が終われば、
「await」の次の行がら処理が再開されます。
スマートですね。
「await」は「子スレッド」の処理終了を待機させる物なので
「子スレッド」の実行開始位置と、「子スレッド」の終了待機の位置を
別の位置に書く事もできます。
以下のような書き方になります。
awaitで子スレッドの処理終了を別の位置で待機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 | private async void button1_Click( object sender, EventArgs e)
{
for ( int i = 0; i < 6; i++)
{
label1.Text = i.ToString();
var task1 = Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
});
await task1;
}
}
|
「Invoke」と「lock」について…
ここでは記述していませんが、
「await」の「Task」内に「Invoke」を使用しても問題ありません。
あと注意として、
ロックは「lock」でなく、「SemaphoreSlim」を使用します。
詳しくは後述します。
「async/await」の戻り値
「async」を付けた「非同期メソッド」は
戻り値の型が
「void」か「Task」か「Task<T>」か「ValueTask<T>」
のいずれかである必要があります。
(「ValueTask<T>」型はまだあまり使われていない為、今回説明を省きます。)
戻り値が「void」以外の時は「非同期メソッド」名の最後に、
「Async」か「TaskAsync」という語尾を付ける決まりになっています。
強制ではありませんが付けた方が無難です。
ただし、
「UIイベント」( button1_Click等)のメソッド名称は変更不要です。
「async」の戻り値3パターン
メソッド内で「await」や「async」が使われているメソッドの
戻り値パターンを紹介します。
戻り値のパターンにより
メソッド内で「return」を書く必要の有無
タスク内の処理状況の把握が可能、不可能
メソッド名の名称変更が必要、不必要
と違いが出ます。
「UIイベント」のみ「void」の戻り値がゆるされますので、
それについても説明します。
「void」型の戻り値を使う場合(パターン1)
「void」型の戻り値は
「UIイベントメソッド(button1_Clickなど)」でしか使ってはいけません。
・「return」は必要ない為、メソッド内に書きません。
・「非同期メソッド」自体の処理状態情報を把握できません。
・「非同期メソッド」名、変更なし。
「イベントメソッド」の戻り値は基本「void」型です。
その「イベントメソッド(コントロール系のイベント)」を
「async」を付けた「非同期メソッド」に変更する時に限り
「void」型の戻り値が許可されます。
「void」型の戻り値では、「非同期メソッド」の処理状態情報を
「非同期メソッド」を実行した側が知ることができません。
その為、
「Task」の完了も報告されず、例外も知る事ができません。
つまり、「子スレッド」へ処理を投げたら投げっぱなしの形になります。
「イベントメソッド」以外を「非同期メソッド」に変更する場合は
この後紹介する「Task」型か「Task<T>」型の戻り値を使用して下さい。
「async/await」の戻り値「void」型1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private async void button1_Click( object sender, EventArgs e)
{
}</t>
|
何度も言いますが、「void」型の戻り値が許されるのは「UIイベント」のみです。
その為「await(非同期)」を使用したコードは必然的に、この後紹介する
「パターン2」や「パターン3」
の記述方法となるはずです。
あまりオススメしないのですが後述する、
「UIイベント」に「async」を書かない方法(パターン1補足)
のような「UIイベント」の書き方も可能です。
ただし本来(非同期メソッドを内包するメソッド)は、
「UIイベント」のメソッドであっても「async」修飾子を付けるべきです。
「Task」型の戻り値を使う場合(パターン2)
特に「戻り値」が必要ない場合は、「Task」型の戻り値を使用します。
・「return」は必要ない為、メソッド内に書きません。
・「非同期メソッド」自体の処理状態情報を知る事ができます。
・「非同期メソッド」名、語尾に「Async」追加。
戻り値のない自作のメソッドを「非同期メソッド」にする場合は、
必ず「Task」型の戻り値を使います。
「async/await」の戻り値「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 | private async void button1_Click( object sender, EventArgs e)
{
for ( int i = 0; i < 6; i++)
{
label1.Text = i.ToString();
var task1 = Wait1secAsync();
await task1;
}
}
private async Task Wait1secAsync()
{
await Task.Run(() => {
System.Threading.Thread.Sleep(1000);
});
}
|
「Task<T>」型の戻り値を使う場合(パターン3)
「戻り値」が欲しい場合「Task<T>」型の戻り値を使用します。
・「return」に戻り値を書きます。
「return」された型は「Task<T>」型で戻り値で受け取ります。
(「int」型を「return」する場合、「Task<int>」型で受け取る。)
・「非同期メソッド」自体の処理状態情報を知る事ができます。
・「非同期メソッド」名、語尾に「Async」追加。
戻り値のある自作メソッドを「非同期メソッド」にする場合は、
必ず「Task<T>」型の戻り値にします。
「await」で「子スレッド」の処理が終了するのを待機してから
「Task<T>.Result」を使い、受け取った戻り値の取り出しを行います。
(「Task<T>.Result」には「Task.Wait()」処理が内包されています。
使用前には「await」を使って「子スレッド」の処理終了を待つ事を
忘れないように注意して下さい。)
この例では、文字列処理をあえて「子スレッド」側で行います。
「async/await」の戻り値「Task<T>」型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 34 35 36 37 | private async void button1_Click( object sender, EventArgs e)
{
var addStr = "" ;
for ( int i = 0; i < 6; i++)
{
label1.Text = i.ToString()+addStr;
var task1 = WaitSecAsync(1000);
await task1;
addStr = task1.Result;
}
}
private async Task< string > WaitSecAsync( int num)
{
var sec = await Task.Run(() => {
System.Threading.Thread.Sleep( num );
return num/1000;
});
return "×" +sec.ToString()+ "秒経過" ;
}
|
ボタンクリックで、
「0→1×1秒経過→2×1秒経過→3×1秒経過→4×1秒経過→5×1秒経過」
と表示されます。
「UIイベント」に「async」を書かない方法(パターン1補足)
「void」型の戻り値は「UIイベント」のみに許されます。
「ActionやFunc」内でラムダ式に「async」を使う事により
「button1_Clickイベント」メソッドを「非同期メソッド」にしないで、
「button1_Clickイベント」メソッドはそのままに、
「Task」型の戻り値でラムダ式の「非同期メソッド」を内包させる事ができます。
以下の形になります。
ラムダ式で「async」「非同期メソッド」を内包1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | private void button1_Click( object sender, EventArgs e)
{
Func<Task> func1 = async () =>
{
for ( int i = 0; i < 6; i++)
{
label1.Text = i.ToString();
await Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
});
}
};
var task0=func1();
}
|
その場限りの「非同期」のテストを行いたい時などに、
とりあえずこの書き方で動作確認してみる事がよくあります。
ただし本来は、
何か特別な意図がない場合は「UIイベント」のメソッドにも
「async」修飾子を付けるべきで、
必然的に
「パターン2」や「パターン3」
の記述方法となるハズです。
「async/await」中のロック、
「Task」と「SemaphoreSlim」
「lock」内に「await」は使えません。
「await」内に「lock」も使えません。
(複数の「await」の「子スレッド」を順に実行した時に、
同じ「スレッド」で動く事が保証されていない為です)
「await」中のロックは「semaphoreSlim」を使います。
「セマフォスリム」は、
事前に「セマフォロック」用のオブジェクトを用意しておいて、
「try{ 〜 }finally{ 〜 }」にからめて使用します。
「セマフォスリム」の使用例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 34 35 36 37 | private System.Threading.SemaphoreSlim _semaphoreLock = new System.Threading.SemaphoreSlim(1);
private void button1_Click( object sender, EventArgs e)
{
Func<Task> func1 = async () =>
{
await _semaphoreLock.WaitAsync();
try
{
for ( int i = 0; i < 6; i++)
{
label1.Text = i.ToString();
await Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
});
}
}
finally
{
_semaphoreLock.Release();
}
};
var task0 = func1();
}
|
これで「button1」を素早く2回クリックした場合、
「0→1→2→3→4→5」「0→1→2→3→4→5」
という「
直列的」表示になります。
「他のスレッド」内で「例外」発生時の注意
「VisualStudio」の「デバッグモード」で、
「子スレッド」や「子スレッド」内「Invoke」等、
「他のスレッド」内で「例外」が起きた際の挙動に注意して下さい。
例えば、
「VisualStudio」で「他スレッド」内での「例外」例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private async void button1_Click( object sender, EventArgs e)
{
try
{
await Task.Run(() =>
{
MessageBox.Show( "タスク処理中" );
throw new Exception( "例外タスク内" );
});
MessageBox.Show( "処理" );
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
MessageBox.Show( "処理終了" );
}
|
は、
『「タスク処理中」→「例外タスク内」→「処理終了」と表示されます。』
と普段は書きますが、
実際の「VisualStudio」上では以下のような表示となります。
↓「タスク処理中」表示

↓
ここで「スレッド」を跨ぐ為、エラー表示。
「ユーザーが処理していない例外」のメッセージ。
(「F5」を押し、「続行」させるだけで大丈夫です。)

↓「例外タスク内」表示

↓「処理終了」表示

この様に、「スレッド」を跨ぐ時に、
「VisualStudio」側のメッセージが介在する事を認識しておいて下さい。
最初から「VisualStudio」上で、
「CTRL+F5」の「デバッグ無しで開始」で実行を開始する事で、
「VisualStudio」側のメッセージの介在を防ぐ方法も有ります。
「async/await」と「例外処理」
「Task」内の例外を拾うには…
「await」「Task.wait()」「Task.result」を
「try〜catch」で囲む事で「Task」内の「例外」を掴めます。
(「await」以外の「例外処理」については補足で説明します。)
ここでは「await」での「例外処理」について説明します。
「Task.Run()」その物を「try〜catch」で囲んでも「例外」を
拾えない事に注意して下さい。
「await」中の「例外」
「Exception」でキャッチできます。
「await」中の「例外」例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private async void button1_Click( object sender, EventArgs e)
{
var task = Task.Run(() =>
{
this .Invoke( new Action(() =>
{
label1.Text = "abcde" ;
}));
System.Threading.Thread.Sleep(1000);
throw new Exception( "例外タスク内" );
});
try
{
await task;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
MessageBox.Show( "処理終了" );
}
|
「abcde」→「例外タスク内」→「処理終了」
と表示されます。
「await」中「Invoke」内の「例外」
「Exception」でキャッチできます。
「Invoke」内の「例外」を「await」内でキャッチ。
「await」内の「例外」を「親スレッド」内でキャッチしています。
「await」内で「例外」を再スローする際、
「throw;」は「throw ex;」としていない所にも注意して下さい。
(Javaの場合の「throw ex;」とC#の「throw;」が同義となります。
「throw;」→throwされる例外「StackTrace」を保持。
「throw ex;」→例外「StackTrace」をその時点から更新。)
「await」中「Invoke」内の「例外」例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 | private async void button1_Click( object sender, EventArgs e)
{
var task = Task.Run(() =>
{
try
{
this .Invoke( new Action(() =>
{
throw new Exception( "例外Invoke内" );
label1.Text = "abcde" ;
}));
System.Threading.Thread.Sleep(1000);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw ;
}
});
try
{
await task;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
MessageBox.Show( "処理終了" );
}
|
「例外Invoke内」→「例外Invoke内」→「処理終了」
と表示されます。
「上位スレッド」のみで「例外」をキャッチしたい場合は、
「Task」内の「Try{ }catch(Exception ex){ 〜 }」そのものを削除してもかまいません。
「Task」内の「例外処理」を残すのであれば、
の様に「(Exception ex)」を省略して再スローさせても、
「上位スレッド」で「例外」をキャッチできます。
「Task.Wait()」と「例外」
「AggregateException」でキャッチできます。
「Task.Wait()」についてはここでは説明しません。
補足の
「Task.Wait()」と「例外処理」の方で説明しています。
他
C# マルチスレッド、非同期の補足
http://1studying.blogspot.com/2017/04/c_22.html
に補足情報を書いておきました。
「Task.Wait()」は「Task」の処理終了を待つ間、
「親スレッド」の動作を止めてしまいます。
「Invoke」や「await」との組み合わせでデットロックが紛れやすいです。
補足の方で説明しています。
「Task.Wait()」の「例外」についても
補足の方で説明しています。
以下サイトを参考にしました。
http://gomocool.net/gomokulog/?p=762
http://kimux.net/?p=902
http://qiita.com/acple@github/items/8f63aacb13de9954c5da
http://blog.xin9le.net/entry/2012/07/30/123150
https://ufcpp.wordpress.com/2012/11/12/asyncawait%E3%81%A8%E5%90%8C%E6%99%82%E5%AE%9F%E8%A1%8C%E5%88%B6%E5%BE%A1/
http://blog.shtair.net/2014/07/29/%E9%9D%9E%E5%90%8C%E6%9C%9F%E3%81%AE%E5%90%8C%E6%9C%9F/
非同期のパターン
http://qiita.com/0xfffffff7/items/f7ed068350ed420e5219
独学なので、認識違いがあったらごめんなさい。
以上。