2012年12月01日
.NET Framework4.5 での非同期I/O処理 事始め
この記事は、C# Advent Calender 2012 http://atnd.org/events/33905 への参加記事です。
今年もC# Advent Calender が開始されました。
なかなか書くネタが見つからず、参加しようかどうか悩んでいるうちに、気が付いたら、空いている日が、
初日の 12/1 と 最終日の 12/25 の2日だけになってました。さすがに最終日はプレッシャーがかかると
思ったので、初日である 12/1 にエントリーさせてもらいました。
さて、お題は、「.NET Framework4.5 での非同期I/O処理 事始め」です。
既に多くの方がブログで async, awaitの記事を書いているので、2番煎じ、3番煎じの感は
否めませんが、まあ、このC# Advent Calenderは、ゆるいイベントということなので、大目
に見てください(^^;
さて、ここからが本題です。
.NET Framework4.5から、非同期IOをサポートする新しいasync系メソッドが追加されています。
これにより、従来よりもより簡単に非同期IO処理が書けるようになりました。
■.NET Framework4.0 以前の非同期I/O処理
まずは、.NET Framework4.0 以前はどうだったかを振り返ってみましょう。
これまでの Streamクラスには、BeginRead, EndRead といった非同期IOをサポートするメソッドが
用意されていました。
従来の非同期IOの簡単なサンプルを以下に示します。
このコードは、Sample.txtファイルを非同期IOで読み込み、TextBox にその内容を表示しています。
※ 今回は、WindowsFormsを使いましたが、WPFでもなんら変わることはありません。
ファイルのサイズが小さければ、こんな面倒なコードを書かずに、File.ReadAllTextで一気に
読み込んでしまって良いと思いますが、巨大なファイルの場合は、UIがフリーズするのを防止できません。
UIのフリーズを防止するには、スレッドを起動するとか、BackgroundWorkerクラスを利用するとか、Taskを使うとか、
いろいろと方法はありますが、同期処理に比べると面倒です。
それと、上記のコードは、テキストファイルを扱うのに、ReadLine系のメソッドを使えないため、byte配列に
読み込んでいて、いまいち感がぬぐえません。
■.NET Framework4.5 での非同期I/O処理
では、C#5.0 + .NET Framework4.5ではどうなるかを見てみましょう。
上記コードの注目点は、以下の4点です。
1. メソッドに、async キーワードが付いている。
2. ReadLineAsync() という非同期IOメソッドを使っている。
3. この非同期IOメソッドに、await キーワードをつけている。
4. 同期処理のように、UIへのアクセスを普通に行っている。
メソッドに、await キーワードを使う場合には、必ずメソッドにasync キーワードをつける必要があります。
そして、await を使うことで、非同期処理をあたかも同期処理のように書くことが
可能になります。
いやー、これはほんとうに便利ですね。
■IProgressによるロジックとUIの分離
でも、ファイルIO処理の中で、UIにアクセスするコードを書くのは嫌だ! と思う人も多いと思います。
大丈夫です。.NET Framework4.5では、そういった要求にも簡単にこたえることができます。
コードを示します。
ReadTextDataメソッドの引数に、Progress
このProgress
ここで、UI処理を書くわけですね。
ReadTextData メソッドの中では、progress.Report(s) と、Reportメソッドを呼び出しています。
このタイミングで、先ほどのコールバックが呼び出されることになります。
これで、ReadTextDataメソッドは、完全にUIとは分離されたので、Formクラスに書く必要もなくなりましたね。
Write系のコードも載せようと思ったのですが、まあ、上記とほとんど同じ感覚で書けるので、
あえて載せる必要もないでしょう。
2012年08月25日
ThreadStatic
staticフィールドは、複数のスレッドで共有されるので、
スレッド間で情報を共有する際に利用できますが、
スレッド毎に独立したstaticフィールドを持ちたい場合があります。
そんな時に、フィールドに ThreadStatic属性を使います。
しばらくぶりに、このThreadStatic属性を使いました。
あまりにもしばらくぶりだったので、その存在自体忘れてました(^^;
スレッド間で情報を共有する際に利用できますが、
スレッド毎に独立したstaticフィールドを持ちたい場合があります。
そんな時に、フィールドに ThreadStatic属性を使います。
しばらくぶりに、このThreadStatic属性を使いました。
あまりにもしばらくぶりだったので、その存在自体忘れてました(^^;
2009年08月09日
BackgroundWorkerのサンプルコード(4)
BackgroundWorkerのサンプルコード(1)
BackgroundWorkerのサンプルコード(2)
BackgroundWorkerのサンプルコード(3)
BackgroundWorkerのサンプルコード(3)で掲載したコードとほとんど同じだけど、バックグラウンド処理の結果を受け取れるようにしました。
終了していないのに、結果を取得しようとした場合は、終了するまで待つようにしてあります。
ついでにExecuteメソッドでは、引数を渡せるようにしてあります。
同期するために利用していたManualResetEventですが、代わりにAutoResetEventを使ってみました。
BackgroundWorkerのサンプルコード(2)
BackgroundWorkerのサンプルコード(3)
BackgroundWorkerのサンプルコード(3)で掲載したコードとほとんど同じだけど、バックグラウンド処理の結果を受け取れるようにしました。
終了していないのに、結果を取得しようとした場合は、終了するまで待つようにしてあります。
ついでにExecuteメソッドでは、引数を渡せるようにしてあります。
同期するために利用していたManualResetEventですが、代わりにAutoResetEventを使ってみました。
2009年08月06日
BackgroundWorkerのサンプルコード(3)
BackgroundWorkerのサンプルコード(1)
BackgroundWorkerのサンプルコード(2)
BackgroundWorkerは、Formが無くたって使えます。
今回のサンプルコードは、ManualResetEventを使って、終了まで待てるようにしています。
クラスMyWorkerをもうちょっと汎用的にしたいところですが、BackgroundWorkerの サンプルを示すことが目的なので、これで良しとします。
このプログラムを実行してみればわかりますが、worker1とworker2がパラレルに実行されているのが確認できます。
BackgroundWorkerのサンプルコード(2)
BackgroundWorkerは、Formが無くたって使えます。
今回のサンプルコードは、ManualResetEventを使って、終了まで待てるようにしています。
クラスMyWorkerをもうちょっと汎用的にしたいところですが、BackgroundWorkerの サンプルを示すことが目的なので、これで良しとします。
このプログラムを実行してみればわかりますが、worker1とworker2がパラレルに実行されているのが確認できます。
2009年08月02日
BackgroundWorkerのサンプルコード(2)
BackgroundWorkerのサンプルコード(1)
今度は、処理の進行状況をプログレスバーで表示する例です。
途中で、処理をキャンセルする例にもなっています。
Form1のボタンをクリックすると、Form2が開き、処理を開始します。
処理の進行状況は、プログレスバーで表示され、処理が終わると 自動的にForm2が閉じるというものです。
Form2には、キャンセルボタンが配置されていて、キャンセルボタンを押すと 処理を中止します。
今度は、処理の進行状況をプログレスバーで表示する例です。
途中で、処理をキャンセルする例にもなっています。
Form1のボタンをクリックすると、Form2が開き、処理を開始します。
処理の進行状況は、プログレスバーで表示され、処理が終わると 自動的にForm2が閉じるというものです。
Form2には、キャンセルボタンが配置されていて、キャンセルボタンを押すと 処理を中止します。
2009年07月29日
BackgroundWorkerのサンプルコード(1)
BackgroundWorkerで検索して、このブログに来ている人が結構いるようなので、何回かに分けて、BackgroundWorkerを使ったサンプルを載せたいと思います。
まずは、バックグラウンド処理で得られたデータをListBoxに順に表示する例です。
ついでに、ListBoxの自動スクロールもやってます。
当然、ListBoxに項目を追加している間でも、ListBoxの操作(選択など)が可能です。
まずは、バックグラウンド処理で得られたデータをListBoxに順に表示する例です。
ついでに、ListBoxの自動スクロールもやってます。
当然、ListBoxに項目を追加している間でも、ListBoxの操作(選択など)が可能です。
2009年07月01日
2009年06月30日
2009年06月24日
2008年09月17日
ThreadStaticAttribute
ThreadStaticAttribute こんな属性があることを知らずに、スレッド毎に一つのインスタンスが保障されるコードを一生懸命C#で書いていました。
会社の後輩にこの属性があることを教えてもらったので、備忘のために書いておきます。
会社の後輩にこの属性があることを教えてもらったので、備忘のために書いておきます。
2007年11月06日
ThreadPoolのMaxThreads
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
を実行したら、workerThreadsの数が 500 だった。
CPU×25だと思っていたのだけど、
CPU×250 みたいだ。いつから変わったのかな?
2007年01月29日
AutorResetEventによる同期
今まで、C#で、AutorResetEventを使ったコードを書いたことがなかったので、
動きを確かめるため、書いてみた。
このコードは、3つのスレッドを起動し、すべてが終わるまで待つというコード。
どれかひとつを待つのならば、WaitAny staticメソッドか、WaitOneメソッドを使えばよい。
WaitOneの場合は、AutoResetEvent のインスタンスはひとつだけでよいので、
コードも簡単だ。(コードは省略)
ManualResetEvent の場合は、Resetをするコードも必要なので、スレッドの終了待ちの
ようなコードは、AutoResetEventの方が楽ですね。
動きを確かめるため、書いてみた。
using System.Threading;
...
private List<AutoResetEvent> areList = new List<AutoResetEvent>();
private void Run() {
for (int i = 0; i < 3; i++) {
AutoResetEvent are = new AutoResetEvent(false);
areList.Add(are);
Thread th = new Thread(Start);
th.Start(are);
}
AutoResetEvent.WaitAll(areList.ToArray());
Console.WriteLine("Thread End");
Console.ReadLine();
}
private Random rnd = new Random();
private void Start(object obj) {
Console.WriteLine("Thread Start");
Thread.Sleep(1000*rnd.Next(1,4));
Console.WriteLine("Thread Stop");
((AutoResetEvent)obj).Set();
}
このコードは、3つのスレッドを起動し、すべてが終わるまで待つというコード。
どれかひとつを待つのならば、WaitAny staticメソッドか、WaitOneメソッドを使えばよい。
WaitOneの場合は、AutoResetEvent のインスタンスはひとつだけでよいので、
コードも簡単だ。(コードは省略)
ManualResetEvent の場合は、Resetをするコードも必要なので、スレッドの終了待ちの
ようなコードは、AutoResetEventの方が楽ですね。
2006年05月29日
BackgroundWorker
先日は、
>BackgroundWorker コンポーネントは、System.Threading 名前空間に代わると
>共に追加の機能を提供します。
とMSDN ドキュメントを引用しましたが、
BackgroundWorkerは、名前のとおり、GUIの裏(バックグラウンド)で処理をさせたいときに利用するためのコンポーネントであって、僕が今やろうとしているスレッドプールを利用した多重処理を実現させるには、ちょっと利用するのが難しそうです。
ただ、WindowsFormで、時間のかかる処理を走らせるにはとても手軽に使えて良いですね。プログレスバーの表示のための仕組みも組み込まれています。
BeginInvoke を使う必要もありません。
先日のコードだと、このあたりが分からないので、再度コードをアップします。
※ ReportProgress には、ユーザデータを渡せるメソッドがオーバーロードされていますので、パーセント以外の情報も表示できます。
>BackgroundWorker コンポーネントは、System.Threading 名前空間に代わると
>共に追加の機能を提供します。
とMSDN ドキュメントを引用しましたが、
BackgroundWorkerは、名前のとおり、GUIの裏(バックグラウンド)で処理をさせたいときに利用するためのコンポーネントであって、僕が今やろうとしているスレッドプールを利用した多重処理を実現させるには、ちょっと利用するのが難しそうです。
ただ、WindowsFormで、時間のかかる処理を走らせるにはとても手軽に使えて良いですね。プログレスバーの表示のための仕組みも組み込まれています。
BeginInvoke を使う必要もありません。
先日のコードだと、このあたりが分からないので、再度コードをアップします。
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork += new DoWorkEventHandler(MyTask);
backgroundWorker1.RunWorkerCompleted += MyTaskCompleted;
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(MyTaskProgressChanged);
// バックグラウンド処理の起動
backgroundWorker1.WorkerReportsProgress = true;
// プログレスバー表示処理
private void MyTaskProgressChanged(object sender,ProgressChangedEventArgs e) {
this.progressBar1.Value = e.ProgressPercentage;
}
// バックグラウンド処理
void MyTask(object sender, DoWorkEventArgs e) {
BackgroundWorker bw = (BackgroundWorker)sender;
...
for ( ... ) {
...
bw.ReportProgress(percentage);
}
}
// バックグラウンド処理が終了したときに呼び出される
void MyTaskCompleted(object sender, RunWorkerCompletedEventArgs e) {
...
}
※ ReportProgress には、ユーザデータを渡せるメソッドがオーバーロードされていますので、パーセント以外の情報も表示できます。
2006年05月27日
BackgroundWorker コンポーネント
MSDN のドキュメントを読んでたら、こんな記述が目にとまった。
BackgroundWorker コンポーネントは、System.Threading 名前空間に代わると共に追加の機能を提供します。
ということは、BackgroundWorker コンポーネントを利用することが推奨されているってことかな。
たしかに、簡単です。
BackgroundWorker コンポーネントは、System.Threading 名前空間に代わると共に追加の機能を提供します。
ということは、BackgroundWorker コンポーネントを利用することが推奨されているってことかな。
たしかに、簡単です。
backgroundWorker1.DoWork += MyTask;
backgroundWorker1.RunWorkerCompleted += MyTaskCompleted;
backgroundWorker1.RunWorkerAsync();
private void MyTask(object sender, DoWorkEventArgs e) {
...
}
private void MyTaskCompleted(object sender,
RunWorkerCompletedEventArgs e) {
...
}
2006年05月09日
ThreadPool.SetMaxThreadsメソッド
.NET Framework2.0 で ThreadPool クラスに SetMaxThreads メソッド が追加されています。
これで、同時にアクティブにできるスレッドの数を設定できるようになりました。
最適なスレッド数を実測して求めるにの利用できそうです。
workerThreads の デフォルト値は、シングルCPUの場合 25ですが、25より大きな値を入れても大丈夫みたいです。
まあ、そんなに大きな値を指定しても、パフォーマンスが悪くなるので、小さく制限することはあっても、25よりも大きな値を設定することは、無いと思いますが...
ThreadPool.SetMaxThreads(workerThreads, completionPortThreads);
workerThreads
スレッド プール内のワーカー スレッドの最大数。
completionPortThreads
スレッド プール内の非同期 I/O スレッドの最大数。
これで、同時にアクティブにできるスレッドの数を設定できるようになりました。
最適なスレッド数を実測して求めるにの利用できそうです。
workerThreads の デフォルト値は、シングルCPUの場合 25ですが、25より大きな値を入れても大丈夫みたいです。
まあ、そんなに大きな値を指定しても、パフォーマンスが悪くなるので、小さく制限することはあっても、25よりも大きな値を設定することは、無いと思いますが...