MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C Sharpの基礎 - マルチスレッドのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C Sharpの基礎 - マルチスレッド
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == .NET Framework 4.5で導入されたC# 5.0(Visual Studio 2012) からは、マルチスレッドプログラミングをコンパイラレベルでサポートしている。<br> 具体的には、asyncとawaitというキーワードが導入された。<br> これは、タスクベース非同期パターン(Task-based Asynchronous Pattern、TAP)を実装するものである。<br> <br> このタスクベース非同期パターンでは、処理の開始はMethodNameAsyncという形式をとる。<br> そしてその戻り値を、Task、Task<T>、voidとし、Taskを待つにはawaitを付加する。<br> また、awaitを付加した関数には、asyncを付加する。<br> <br> 下記の例は、Task<int>型を返す非同期関数である。<br> <syntaxhighlight lang="c#"> public async Task<int> MethodAsync() { var iRet = await Task.Run(() => { await Task.Delay(5000); return 0; }); return iRet; } </syntaxhighlight> <br><br> == asyncとawaitの動作 == タスクとは、一連の処理をひとまとまりにした単位である。<br> <code>Task</code>クラスはマルチスレッドで実行されるようになっており、連続で記述する場合は、記述した個数だけマルチスレッドで並列処理される。<br> <br> <code>Task.Run</code>メソッドは、大量の計算のみを行う処理を並列実行させる時のみに使用する場合、または、既存のメソッドを変更せずに使用する場合に使用する。<br> <syntaxhighlight lang="c#"> public async Task MethodAsync() { var task1 = Task.Run(() => { // 何か処理1 }); var task2 = Task.Run(() => { // 何か処理2 }); var task12 = Task.WhenAll(task1, task2); var taskAll = task12.ContinueWith(() => { // 何か処理3 }); return await taskAll.ConfigureAwait(false); } </syntaxhighlight> <br> <code>Task.Run</code>メソッドには、引数を渡すオーバーロードが存在しない。<br> 引数を渡す場合、<code>Task</code>クラスのインスタンスの生成と同時に引数を渡して、<code>Task</code>クラスの<code>Start</code>メソッドを使用してスレッドを実行する必要がある。<br> <syntaxhighlight lang="c""> private async void MethodAsync() { int m = 999; Task<int> task = new Task(x => { Thread.Sleep(3000); return x * 2; }, m); task.Start(); m = 100; int result = await task; // result : 1998 Console.WriteLine($"{result}"); } </syntaxhighlight> <br><br> == Wait / Result == コンソールソフトウェアにおいて、<code>Wait</code>メソッドおよび<code>Result</code>メソッドを使用して、非同期メソッドを作成することができる。<br> <syntaxhighlight lang="c#"> HttpClient hc = new HttpClient(); string html = hc.GetStringAsync("http://example.jp/").Result; </syntaxhighlight> <br> また、<code>Wait</code>メソッドおよび<code>Result</code>メソッド以外に、<code>WaitAll</code>メソッドや<code>WaitAny</code>メソッドが存在する。<br> <syntaxhighlight lang="c#"> HttpClient hc = new HttpClient(); Task<string> t1 = hc.GetStringAsync("https://www.microsoft.com/"); Task<string> t2 = hc.GetStringAsync("https://www.bing.com/"); // タスクが終わるまでスレッドをブロック t1.Wait(); // タスクが終わるまでスレッドをブロックして結果を取得 string binghtml = t2.Result; // どれかのタスクが終わるまでスレッドをブロック int completedTaskIndex = Task.WaitAny(t1, t2); // 0 : タスクt1の方が速い場合 // 1 : タスクt2の方が速い場合 // どれかのタスクが終わるまでスレッドをブロック(タイムアウトあり) int completedTaskIndex2 = Task.WaitAny(new[] { t1, t2 }, 50); // 0 : タスクt1の方が速い場合 // 1 : タスクt2の方が速い場合 // -1 : タスクt1およびt2とも50[ms]以内に応答がない場合 // 全てタスクが終わるまでスレッドをブロック Task.WaitAll(t1, t2); // 全てのタスクが終わるまでスレッドをブロック(タイムアウトあり) bool allTasksCompleted = Task.WaitAll(new[] { t1, t2 }, 50); // true : タスクt1およびt2とも50[ms]以内に応答がある場合 // false : タスクt1またはt2のいずれか一方、または、タスクt1およびt2とも50[ms]以内に応答がない場合 </syntaxhighlight> <br><br> == WhenAny == 複数のタスクのうち、いずれか1つのみが完了するまで待機する場合、<code>WhenAny</code>メソッドを使用する。<br> <syntaxhighlight lang="c#"> HttpClient hc = new HttpClient(); Task<string> t1 = hc.GetStringAsync("https://www.microsoft.com/"); Task<string> t2 = hc.GetStringAsync("https://www.bing.com/"); Task<string> completedTask = await Task.WhenAny(t1, t2); </syntaxhighlight> <br> これを応用して、処理継続中のタスクをキャンセルすることもできる。<br> <syntaxhighlight lang="c#"> HttpClient hc = new HttpClient(); CancellationTokenSource cts = new CancellationTokenSource(); // GetStringAsyncメソッドにはCancellationToken構造体を受けるオーバーロードが無いため、GetAsyncメソッドを使用している Task<HttpResponseMessage> t1 = hc.GetAsync("https://www.microsoft.com/", cts.Token); Task<HttpResponseMessage> t2 = hc.GetAsync("https://www.bing.com/", cts.Token); // 完了したタスクを取得 Task<HttpResponseMessage> completedTask = await Task.WhenAny(t1, t2); // 他の処理中のタスクは全てキャンセルする cts.Cancel(); // 完了したタスクの結果を取得 HttpResponseMessage msg = await completedTask; string html = await msg.Content.ReadAsStringAsync(); </syntaxhighlight> <br><br> == WhenAllメソッド / ContinueWithメソッド == <code>Task</code>クラスは<code>ContinueWith</code>メソッドを使用して、その後の処理を連続して実行できる。<br> <br> 例えば、2つ以上のタスクを実行して全てのタスクが完了した後に続けてタスクを実行する場合、<code>ContinueWith</code>メソッドまたは<code>await</code>を使用してタスクの完了を待つ。<br> <syntaxhighlight lang="c#"> // パターン1 var task1 = Task.Run(() => { // 何か処理 }); var task2 = Task.Run(() => { // 何か処理 }); var task3 = Task.Run(() => { // 何か処理 }); var taskAll = Task.WhenAll(task1, task2).ContinueWith(task3); // awaitは不要 </syntaxhighlight> <br> <syntaxhighlight lang="c#"> // パターン2 var task1 = Task.Run(() => { // 何か処理 }); await task1.ConfigureAwait(false); var task2 = Task.Run(() => { // 何か処理 }); await task2.ConfigureAwait(false); var task3 = Task.Run(() => { // 何か処理 }); await task3.ConfigureAwait(false); </syntaxhighlight> <br> 状況にもよるが、処理が膨大になると可読性が落ちるため、先にタスクをまとめて記述して、タスク間の関係性が見通せるパターン1の方が良い。<br> <br><br> == IsCompletedプロパティ == タスクが完了しているかどうかを確認する場合、<code>IsCompleted</code>プロパティを使用する。<br> <br> <code>IsCompletedSuccessfully</code>プロパティもあるが、これはタスクが成功および完了した場合のみ<code>true</code>になる。<br> また、<code>IsFaulted</code>プロパティや<code>IsCanceled</code>プロパティも存在する。<br> <syntaxhighlight lang="c#"> HttpClient hc = new HttpClient(); Task<string> task = hc.GetStringAsync("https://www.microsoft.com/"); // タスクが成功および完了したかどうかを確認 if (!task.IsCompleted) { // この時点でタスクt1が成功および完了している場合、このメッセージは表示されない Console.WriteLine("ちょっとまってね"); } var html = await task; Console.WriteLine(html); </syntaxhighlight> <br><br> == ConfigureAwait(false) == GUIアプリケーションにおいて、<code>Wait</code>メソッドおよび<code>Result</code>メソッドを使用する場合、デッドロックが発生する。<br> これは、<code>Task</code>クラスの<code>Wait</code>メソッドや<code>Result</code>メソッドは、元のスレッドへ戻るからである。<br> <br> 以下の例では、まず、methodAsync().Wait()を実行した時点で親スレッドがスリープする。<br> 次に、methodAsyncメソッドの完了後、親スレッドに戻る時、既に親スレッドがスリープしているため、処理を続けることができない。<br> そのため、親スレッドは子スレッドを待ち続け、子スレッドは親スレッドに戻ろうとして、デッドロックが発生する。<br> <syntaxhighlight lang="c#"> // デッドロックが起きるサンプルコード(GUIアプリケーションのみ) // コンソールアプリケーションでは正常に動作する public void callMethod() { methodAsync().Wait(); } public async Task methodAsync() { return await Task.Delay(1000); } </syntaxhighlight> <br> 対策として、<code>ConfigureAwait(false)</code>を使用するとデッドロックが回避できる。<br> <br> <code>ConfigureAwait(false)</code>は、メソッドの終了後に親スレッド(呼び出し元)に戻らなくてもよいことを指定する。<br> ブロックされたスレッドに戻らなくなるため、methodAsyncメソッドが終了した時にcallMethodメソッドに戻ることができる。<br> <syntaxhighlight lang="c#"> // デッドロックが起きないサンプルコード(GUIアプリケーションのみ) public void callMethod() { methodAsync().Wait(); } public async Task methodAsync() { return await Task.Delay(1000).ConfigureAwait(false); } </syntaxhighlight> <br><br> == Parallelクラス == CPU処理を並列実行する場合、<code>Parallel</code>クラスを使用する。<br> <br> 以下の例では、methodAsync1メソッド、methodAsync2メソッド、methodAsync3メソッドが並列実行される。<br> <u>ただし、CPUのコア数やスレッドプールの空き状況によっては、並列実行されない場合もあることに注意する。</u><br> <br> なお、<code>Parallel</code>クラスには、<code>Parallel.Invoke</code>メソッドの他に<code>Parallel.For</code>メソッドや<code>Parallel.ForEach</code>メソッドも存在する。<br> また、<code>Parallel.Invoke</code>メソッドの第1引数の<code>ParallelOptions</code>クラスは省略可能である。<br> <syntaxhighlight lang="c#"> // Invokeメソッドの引数はAction型 Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = 4 }, // 最大同時並列数:4 methodAsync1, // 戻り値が無いAction型の場合 () => { methodAsync2(100); }, // 戻り値があるFunc型の場合 () => { // 直接処理を記述してもよい int n = 400; methodAsync3(n); } ); </syntaxhighlight> <br><br> == PLINQ(ParallelEnumerable) == <code>LINQ</code>(<code>PLINQ</code>)を使用して、並列実行することもできる。<br> <code>PLINQ</code>では、<code>AsParallel</code>拡張メソッド以降のコードが並列化される。<br> <br> 以下の例では、処理A(ファイルの読み込み)はシングルスレッドで実行、処理B(ハッシュ値計算)はマルチスレッドで実行している。<br> なお、<code>AsParallel</code>拡張メソッドの実行後の<code>WithDegreeOfParallelism</code>拡張メソッドは省略可能である。<br> <syntaxhighlight lang="c#"> // ファイルのハッシュ値を計算するプログラム var files = Directory.GetFiles(Environment.SystemDirectory, "*.exe"); var filehash = files .Select(f => new { File = f, Data = File.ReadAllBytes(f) }) // 処理A(ファイルの読み込み) .AsParallel() .WithDegreeOfParallelism(4) // 最大同時並列数:4 .Select(f => new { File = f.File, Hash = SHA256.Create().ComputeHash(f.Data)}) // 処理B(ハッシュ値計算) .ToArray(); </syntaxhighlight> <br><br> == 例外処理 == <code>Task</code>クラスや<code>Parallel</code>クラスで発生する例外は、例外<code>AggregateException</code>としてキャッチすることができる。<br> <syntaxhighlight lang="c#"> try { Parallel.Invoke( () => throw new ArgumentException(), () => throw new InvalidOperationException(), () => throw new FormatException() ); } catch (AggregateException exception) { // 原因となるExceptionを確認する場合、FlattenメソッドのInnerExceptionsプロパティを使用する var exceptions = exception.Flatten().InnerExceptions; foreach (var ex in exceptions) { Debug.WriteLine(ex.GetType()); } } catch (Exception ex) { // 未処理の例外をキャッチ Trace.WriteLine($"Log: {ex}"); } catch { // SEHをキャッチ Trace.WriteLine("Log: Caught uninterpreted exception."); } </syntaxhighlight> <br> ただし、<code>Task</code>クラスを<code>await</code>する場合、個別の<code>Exception</code>クラスを使用してキャッチすることもできる。<br> <syntaxhighlight lang="c#"> try { var addresses = await Dns.GetHostAddressesAsync("example.jp"); } catch(SocketException exception) { Debug.WriteLine(exception.ToString()); } catch (Exception ex) { // 未処理の例外をキャッチ Trace.WriteLine($"Log: {ex}"); } catch { // SEHをキャッチ Trace.WriteLine("Log: Caught uninterpreted exception."); } </syntaxhighlight> <br> <code>Task</code>クラスで発生したキャッチされていない例外をまとめて処理する場合、<code>TaskScheduler.UnobservedTaskException</code>メソッドを使用する。<br> <u>ただし、これは、例外が発生したタイミングでキャッチできないこと、および、例外をキャッチできたとしても対処可能なことが限られることに注意する。</u><br> <syntaxhighlight lang="cpp"> private async void buttonl_Click(object sender, RoutedEventArgs e) { await Task.Run(() => { await Task.Delay(1000); }); // 非同期処理の未処理例外をキャッチする TaskScheduler.UnobservedTaskException += (sender, e) => { AggregateException ae = e.Exception; Debug.WriteLine(ae.ToString()); e.SetObserved(); // .NET Framework 4.0の場合、これを記述しないとソフトウェアが強制終了する }; } </syntaxhighlight> <br><br> __FORCETOC__ [[カテゴリ:C_Sharp]]
C Sharpの基礎 - マルチスレッド
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse