第8回まどべんよっかいち「LINQ基本のキ」

第8回まどべんよっかいちで、「LINQ基本のキ」というタイトルで登壇してきました。

時間が短かったので、スライドはコンパクトにまとめ、ディスカッションで補足しました。内容としては、LINQというキーワードは知っているが、全然使った事が無い・SQLと何が違うの?それおいしいの?・クエリが全然読める気がしないんだけど… という方向けの、取り掛かり的なものです。

クエリの中から参照するローカル変数の扱い(評価タイミング)や、そもそも集合演算に慣れるにはどうすれば良いか、といった、慣れてしまった側から見ても有意義な質問が頂けて、楽しかったです。

懇親会で、次回にエントリーしてしまったので、次回はasync/awaitについて、同様な話をしたいと思っています。

スライドはこちら(とても重要な所にアニメーションを使っているので、是非オリジナルで見て下さい):LINQ基本のキ_2

#ちなみに、人、来ました(ほっ)

ご清聴ありがとうございました。
それではまた。

Async訪ねて3000里 (2) : ファイルシステム・パーティションマネージャ・物理ディスクデバイスドライバ

前回は、TaskCompletionSourceクラスの触りと、Win32 APIのReadFileExを使った非同期処理について書きました。ここまではいわゆる「ユーザーモード」プロセスでの処理ですが、この先はWindows内部へと入っていきます。

ReadFileExの呼び出しでは、ユーザーモードからカーネルモードへの遷移が行われた後、ntoskrnl.dll(プラットフォームによって異なる場合がある)内の「ZwReadFile」カーネルモードAPIが呼び出されます。このAPIは最初から非同期処理が前提です。

Async2

殆どのカーネルモードAPIは非同期で処理を行う事を前提としていて、俗にいう「NTSTATUS」という型の戻り値として「STATUS_PENDING」を返すことで、非同期処理を開始した事を示します。勿論、NTSTATUSを扱うすべてのAPIが非同期処理を可能としているわけではありませんが、とりわけ外部リソース(ディスクやネットワークなど)にアクセスする類の呼び出しは、非同期処理を行う事が出来るように設計されています。

ZwReadFileが非同期処理前提であると言う事は、ユーザーモードのReadFileExやReadFile APIを同期モードで使用した場合はどうなるのでしょうか? そのような場合は、ZwReadFileの呼び出しは非同期処理として実行しつつ、その非同期処理が完了するのを、「手元」のスレッドをハードブロックする事で同期的に処理されるようにしています。

ハードブロックとは、「WaitForSingleObject」APIを使用して、完了が通知されるまでスレッドを停止させる事です。但し、今回の例では最終的にAPCコールバックを使用するため、実際にはなじみの薄い「WaitForMultipleObjectsEx」APIを使用する事になります(APCコールバックについては、復路で説明します)。


カーネルモードでの入り口は、ファイルシステムドライバです。ほとんどの場合、NTFSドライバが処理します。ZwReadFile APIが受け取った、対象のファイル・対象のファイル中の位置を元に、ディスク(実際にはパーティション)上のどこにそのデータがあるのかを特定します。この時、カーネルのメモリマネージャと裏取引(公開されていない)を行い、空きメモリをキャッシュ用に割り当てて、結果を保持しておくか、以前に保持したデータがキャッシュ上に存在しないかを確認し、存在すればI/Oを発生させず、非同期処理をその場で完了させる、などの処理を行います。

そのため、実際にディスクからデータを非同期で読み取るには、これらの条件に引っかからずに通過する必要があります。CreateFile APIのFILE_FLAG_NO_BUFFERINGフラグは、この処理を確実にバイパスさせます。

ファイルシステムドライバを通過した要求は、パーティション上の相対位置でデータの位置が示されます。つまり、次の窓口は「パーティションマネージャ」です。目的とするパーティションと相対位置を示すアドレス値から、物理ディスク上の絶対アドレス値への変換を行います。また、より複雑な、Windows標準のソフトウェアRAID処理(0・1・5)もここで行われます。

どのような場合であれ、これらの処理もまた非同期操作が前提となっているので、要求は更に下層にそのまま非同期処理として送られる可能性があります。なお、ファイルシステムドライバから先の要求には、明確なAPIシンボル名が存在しません。WDM(Windows Driver Model)は、各ドライバが階層化されたスタック構造となっていて、要求がバケツリレーのように次のドライバへと送られます。エントリポイントはただ一つですが、データの読み取り要求として、「IRP_MJ_READ」という識別子が割り当てられています。


そして、いよいよ物理ディスクのデバイスドライバまで要求が到達します。この先にも更にスタックされたドライバが存在する可能性があるのですが、話が複雑になるので割愛します。少なくともここに到達すると、物理ディスクの読み取り対象アドレス値が明確になっている筈です。従って、この値を物理ディスクに指示する事で、データを読み取る事が出来ます。

その場で応答が返って来ればの話ですが。

そうです。例えばハードディスクドライブの場合、「セクタ123456から4セクタ読み取ってほしい」と指示しても、データの読み出しが完了するのは、途方もなく未来の話です、CPUにとっては。

そこで、次のようにします。「セクタ123456から4セクタ読み取ってほしい。読み取ったデータは物理メモリ空間の0x12345000に格納して欲しい。終わったら呼び出してね」とディスクコントローラーに指示しておきます。そして、例の「STATUS_PENDING」を戻り値として返します。

STATUS_PENDINGを戻り値として受け取ったパーティションマネージャは、そのまますぐにファイルシステムドライバにもSTATUS_PENDINGを返します。ファイルシステムドライバもまた、STATUS_PENDINGを返します。この値はZwReadFile APIの戻り値として、やはりそのまま返され、カーネルモードからユーザーモードに遷移し、ReadFileEx APIが「ERROR_IO_PENDING」という、STATUS_PENDINGのユーザーモード表現の値として返します。

ReadFileEx APIがERROR_IO_PENDINGを返したと言う事は、非同期処理が開始されたことを意味します。つまり、バックグラウンドでのデータ読み取りが始まったことになります。今までの経路を見た通り、この状態に至るまでにワーカースレッドは生成も使用もされていません。従って、CPUはその間に自由に動く事が出来るようになります。

実際に非同期の処理を行っているのは、正にハードウェアそのもの、と言う事が出来ます。
これが、非同期処理がもっとも効率が高いと考えられる理由です。また、async/awaitによる記述だけで、ここまでデバイスに密着した動きをさせながら、かつ、低レベルAPIの直接使用を不要とする事が出来る事が画期的です。

次回は、復路の複雑な動きを解説します。

Async訪ねて3000里 (1) : ユーザーモードのターン

C# 5.0から、本格的に非同期処理を行うための土台が整いました。何回かの連載で、非同期処理の意味を、Windowsのカーネルレベルにまで掘り下げて紹介したいと思います。

もう周知だと思いますが、C# 5.0では新たに「async」と「await」という予約語が導入され、非同期処理が簡単に書けるようになりました。この効果は絶大で、過去に様々な非同期処理に関する補助ライブラリが出現しては消え、と言う事を繰り返し、結局どれもが市民権を得られなかったのとは対照的です(非同期処理の種類については「避けて通れない「非同期処理」を克服しよう (@IT)」が詳しいので参照して下さい)。

async/awaitが無くても、非同期処理を記述する事は可能です。これは丁度、LINQが使えなくてもループ処理でリストを操作できるのと似ています。そして、async/waitを使えば、実装がはるかに容易になるという点でもまた、LINQとよく似ています。同時に、async/awaitの内部処理(これはC#コンパイラが頑張っている所ですが)もLINQと良く似ています。

async/awaitの予約語を使うと、コンパイラがどのように処理を行うのかは、「非同期メソッドの内部実装とAwaitableパターンの独自実装 (@IT)」の記事に詳しいので説明を省きます。

この連載では、実際に非同期APIを使うと、内部ではどのように処理されるのかに焦点を当てます。なお、.NET CLRやWindowsカーネルの内部実装を「透視」したわけではないので、現実の実装とは異なる場合があります。非同期処理の道筋についての解釈と思って下さい。


私たちはC#の世界に住んでいるのでC#で説明しますが、とりあえずVB.NETについても非同期処理を行うと同じ過程を踏むはずです。他のCLR対応言語は…awaitに対応するものがあるのかどうか、良く分かりません。

Async1

まず、ユーザーコードから、非同期処理対応のAPIを呼び出すことから始まります。これは、.NET 4.0で導入された「Task」クラスのインスタンスを返却するメソッドとして定義されています。例として、ファイルからデータを読み取る「ReadAsync」という架空のメソッドを考えます。

ReadAsyncは、バックグラウンドで非同期的にデータを読み取り、結果を返します。これを従来の技術でユーザーレベルで実現する場合、ワーカースレッドを生成し、その別スレッド内でファイルの読み取り処理を行い、結果が得られた時点で元のスレッドにデータを通知する、という手順を踏むと思います。

しかし、ネイティブな非同期処理(非同期I/O、といった方が良いので、以降では変えます)では、そもそもワーカースレッドに処理を頼ることはしません。ワーカースレッドが処理をせず、どうやって非同期I/Oが実現するのか?は、おいおい明らかになります。

まず、ReadAsyncの「出口」を固めます。出口とは、データの読み取り処理が完了した事をどのようにユーザコードに通知するのか、という点です。
ユーザーコードは単純に「await」予約語を使って待つか、あるいは戻り値の「Task」を使って待機する事が考えられます。

ワーカースレッドの実行と待機を行うのに、「Task.Run」メソッドを使い、返されたTaskインスタンスをWaitメソッドで待機した事があるのでは無いでしょうか。あるいは、ContinueWithメソッドを使って、ワーカースレッドの継続処理を登録して処理させるなど。つまり、Taskクラスのインスタンスは、非同期処理の結果を操作する役目を持っています。

しかし、今回はワーカースレッドは使いません。そのため、別の方法で、非同期I/Oの終了を通知するためのTaskクラスのインスタンスを入手する必要があります。「TaskCompletionSource」クラスは、この目的に使用する事が出来ます。

TaskCompletionSourceクラスは、特定のワーカースレッドに紐づきません。単に、非同期処理の結果だけを管理します。このクラスにはTaskプロパティがあり、TaskCompletionSourceに非同期処理の完了が通知されると、このTaskプロパティのインスタンスを通じて、その結果が(Taskで待っている処理に)通知されます。そして、awaitから処理が継続したり、Waitメソッドから抜けたりするわけです。

この完了時の動作は、いずれ旅路の復路で明らかになるので、ここまでとしておきます。
重要なのは、

  • 非同期I/Oはワーカースレッドを使って実現するわけではない。
  • TaskCompletionSourceを使って、Taskインスタンスを通じて、処理の完了が通知される。

と言う事です。


次に、ReadAsyncメソッドは、P/Invokeなどを使用してWin32の「ReadFileEx」APIを呼び出します。このメソッドは、一般的な「ReadFile」APIに付加機能が付いたものです。C++でWin32レベルのコードを書いた人であれば、ReadFile APIの同期的な使用方法は容易に理解出来ると思います。

ReadFile及びReadFileEx APIには、「OVERLAPPED」構造体が指定出来ます。この構造体は、非同期I/O要求の管理を行うためのもので、ファイル上のデータのオフセットや、完了時のイベント通知の為の情報を保持します。この構造体を指定しない場合は、I/O操作が同期的に実行されます。ほとんどの方はこの引数にNULLを指定したと思います。また、OVERLAPPED構造体を使用する場合は、ファイルハンドルがFILE_FLAG_OVERLAPPEDフラグを指定してオープンされている必要があります

上記のリンクを見て分かる通り、このKBはとても古く、「Windows NT 3.51」と書かれてます。実際、非同期I/Oは最初のWindows NTカーネル(Windows NT 3.1)からサポートされています。つまり、非同期I/Oの土台としては、現在のWindowsが生まれた時から既に存在すると言う事です。

ReadFileEx APIは何が拡張されているのかというと、「完了ルーチン」なる、コールバックメソッドへのポインタが指定可能になっています。これは、非同期I/O処理が完了した際に、ここで指定されたコールバックメソッドが呼び出されると言う事です。

図中では「APC Callback」と書いた箱がそのコールバックメソッドの実装に対応するわけですが、このメソッドがどのようにして呼び出されるのかは、またかなり大がかりな話となるので、復路で述べることにします。

とにかく、これで、ユーザーコードがReadAsyncメソッドを通じて非同期の読み取り操作を開始し、それがReadFileEx APIを通じてWindowsに要求されたところまで来ました。次は更にこの下に潜ります。

SignalR ブートキャンプ on Windows Azureイベント

地理冗長の中心でAzure愛を叫ぶ (名古屋で、Windows Azure ローンチ4周年とJapan Geo誕生を祝うイベント)

という、中部圏のWindows Azureイベントが開催され、登壇してきました。
私のお題は、「SignalR ブートキャンプ」で、SignalRを使った通信の取り掛かりの解説といった内容です。OWINについてもさらっと取り上げています。

本当は、開催と同時にプレゼンとコードを公開したかったのですが、ちょっと未整理が過ぎたので、後日公開のお約束をさせていただきました。で、本日公開いたします。

この発表のハイライトは、ホワイトボードアプリのデモでした。発表中に、実際にAzure上にホストされたサーバーとクライアントアプリ(SilverlightとWPFによるClickOnce、そして今話題沸騰中のWindows Phone :-) をSignalRで接続し、リアルタイムにホワイトボード共有を実演しました。

#クラウディアさんの分身にはお世話になりました
#ライブコーディングは今後の課題ということで(汗

プレゼン作成中はもちろん検証しているのですが、実際に多人数から同時に使用されたのは初めてで、ぶっつけ本番でしたが、何事もなくほっとしています。と同時にあっさり動いてしまう所が、Windows Azure、本当に魅力的です。

プレゼンです:SignalR ブートキャンプ

コードはGitHubで公開しました:AzureSignalRDemonstration

イベント終了後の懇親会も盛り上がりました!
今回のAzureデータセンター日本リージョン開所記念で、Japan Windows Azure User Groupの中部圏「JAZUG名古屋」もお披露目されました。

また、MiCoCiは中部圏のWindows系技術勉強会の開催などやってます。興味のある方はDSTokaiカレンダーあたりをチェックしてみて下さい。

近日では、Bar Windows Azureの開催を計画しています。

それではまた!

Windows Azure Compute Emulator + IIS Expressで外部IPからの要求を受信可能にする

Visual StudioでWindows AzureのWebロール開発をする場合、プロジェクトテンプレートから生成すると、普通にIIS Express+Compute Emulatorでの開発・デバッグを行うと思います。

この時、Webプロジェクトのデバッグ設定でIIS Expressを使い、”localhost”+ランダムなポート番号を割り当て、Webプロジェクトそのものでデバッグを開始すると、その通りのバインディングで要求を受け付けるのに、Cloudプロジェクトからデバッグ実行すると、何故かポート番号が「81」とかに勝手に強制されたり、localhost(127.0.0.1)以外のIPアドレス・インターフェイスからの要求を受け付けない等で困ったことが無いでしょうか?

私の中でも長らく謎でしたが、業を煮やして調べることにしました。
(なお、現在のWindows Azure SDKは2.2です)

というのも、Windows PhoneからIIS Expressに接続させるようなアプリをデバッグする場合に、WP7までのエミュレーターは、WP内ネットワークのシミュレートを、ホストマシンのネットワークに直接バイパスする機能があったため、WPから「localhost」の指定でIIS Expressに問題なく接続できていました。

WPEmulatorNetwork

ところが、WP8になってエミュレーターがHYPER-Vベースになった関係で、ネットワークシミュレートは単にHYPER-Vの仮想スイッチへの接続となってしまい、ホストマシンのIPアドレス(又はホスト名)を指定しないと接続出来なくなったのです。すると、そもそもIIS Expressはデフォルトでlocalhost以外のインターフェイスをバインドしていないため、全く要求を受け付けず、デバッグ出来ません。

また、Cloudプロジェクトでデバッグを開始すると、ポート番号が勝手に変更されるだけでなく、何で「80」じゃなくて「81」(又はその付近のポート番号)に強制されるのか? ですが、WindowsのIIS(IIS Expressではなく)が80を使用している事によって引き起こされるようです。

これは想像出来たものの、ややこしすぎる orz

で、環境の都合などどうでもいいので、ちゃんと指定された通りに動いてほしい。元々ランダムなポート番号を使うのだから、そのまま素直に使ってくれれば良いでしょ?というのが動機です。

具体的には、以下の対応を行います。

  1. Visual Studioは「管理者権限」で起動する
    正確には、IIS Expressを管理者権限で起動すれば良いのですが、デバッグ時はVSから起動するので、VSを管理者権限で起動します。その理由は3へ。
  2. ファイアーウォールに穴をあける。HYPER-Vからは仮想スイッチ経由で接続されるので、穴があいている必要があります。セオリー通り、最初はファイアーウォールをオフで試して、全て確認できたら明示的に穴を開けるという手順で行きましょう。
  3. IIS Expressの構成ファイルのサイトバインディング情報を「手動修正」して、すべてのネットワークインターフェイスでリッスンさせる。localhost(ループバックインターフェイス)だけではなく、全てのインターフェイスでリッスンを行うためには、「管理者権限」が必要です。これが1の理由となります。そして、IIS Expressの構成ファイルを修正しますが、パスは「マイドキュメントIISExpressconfigapplicationhost.config」です。sitesタグ内のbindingInformation属性を修正します。デバッグ中のサイトに対応するsiteエレメントを探してください。
<site name="AzureServiceSample" id="23">
   <application path="/" applicationPool="Clr4IntegratedAppPool">
      <virtualDirectory path="/" physicalPath="C:PROJECTAzureServiceSampleAzureServiceSample" />
   </application>
   <bindings>
      <binding protocol="http" bindingInformation="*:45934:" />   <!-- ココ -->
   </bindings>
</site>

CloudProjectProperty

上の例の「bindingInformation」属性に、「*:45934:」のように、コロン2個で区切られた文字列を指定します。左側はバインドするインターフェイスに対応するIPアドレスを指定(今回は全てのインターフェイスでリッスンさせるので、アスタリスク)、中央はポート番号、右側はホスト名(HTTPヘッダのホスト名に対応し、マルチホーム識別に使うもの)です。上記の例は単純に、すべてのインターフェイスをリッスン、ポート番号として45934、マルチホームは使わない、と言う事になります。

そして、CloudプロジェクトでCompute Emulatorとセットでデバッグする場合は、このように「Webプロジェクトポートの使用」をTrueに変更します。これで、Cloudプロジェクトデバッグ時も同じ構成(ポート番号)でデバッグ出来ます。

所で、Cloudプロジェクトでデバッグを開始すると、「applicationhost.config」ファイルは上記のパスではなく、テンポラリフォルダに生成されたファイルを読みに行きます。そのため、このファイルを手でカスタマイズしていると、Cloudプロジェクトのデバッグ実行では全く反映されず、これまた悩みます。XMLなので手で簡単にいじれます、が、実際には触れないと言う事です。

WindowsFirewallInboundUnblockRule

ファイアーウォールに穴をあける場合、Windowsファイアーウォールの「受信の規則」で、このような感じでリモートIPアドレスを普段の社内・宅内ネットワーク・あるいは固定IPならそのアドレスに絞り込んでおくと良いでしょう。本当はポート番号も絞りたいところですが、ランダム番号なので、プロジェクト生成時に毎回メンテナンスしなければならず、不便です。もちろん、これは開発時かつ安全な環境である事が前提です。

これで、以下のようにクリーンな開発が出来ます。

  1. 空のWebプロジェクトを作って、一から実装する(ウィザードが作る余計なコードを除外)。
  2. このWebプロジェクトで普通にIIS Expressで普通にデバッグして完成させる。
  3. ここで初めてCloudプロジェクトを追加。
  4. Cloudプロジェクトからデバッグ実行して、Compute Emulator+IIS Expressでも全く問題なく動作する事を確認。
  5. デプローイ!!!

ちなみに、今回はクライアントをWindows Phoneエミュレーターとしましたが、例えばWPの実機や、WCFを使うWPFやWinFormsのクライアントでも、全く同じ手順で問題を回避出来ます。

自分の覚書を兼ねて書きましたが、正直もうちょっとフレンドリーであって欲しいと思います(次回また忘れそうだ)>VS

Microsoft.AspNet.SignalR.Client for Portable Class Library

第二弾。

タイトルの通り、SignalRの.NETクライアントライブラリのPortableバージョンです。
NuGetで公開しているので、すぐに使えます。「Microsoft.AspNet.SignalR.Client.Portable」で探してください。
(CodePlex: Microsoft.AspNet.SignalR.Client for Portable Class Library

例によってVS2012でビルドしました。Xbox360は外れますが、Windows Phone 7.5はOKです。現在オフィシャルで配布しているNuGetのClientライブラリはWindows Phone 7.5が駄目なので、そっち方面の人にどうぞ。
インターフェイスに変更はありません。

こんなコードをクライアントに書いて(例はWP7.5):

// Page class (XAML code behind)
public partial class MainPage : PhoneApplicationPage
{
	private HubConnection client_;
	private IHubProxy sampleHub_;

	// Constructor
	public MainPage()
	{
		InitializeComponent();
	}

	// Page loaded
	private void PhoneApplicationPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
	{
		if (client_ == null)
		{
			client_ = new HubConnection("http://localhost:27224/");
			sampleHub_ = client_.CreateHubProxy("SampleHub");

			sampleHub_.On(
				"Receive",
				new Action<string>(
					x => this.Dispatcher.BeginInvoke(
						new Action(() => listBox.Items.Add(x.ToString())))));

			client_.Start();
		}
	}

	// Button clicked
	private void sendButton_Click(object sender, System.Windows.RoutedEventArgs e)
	{
		var r = new Random();
		var message = string.Format("Random={0}", r.Next());
		sampleHub_.Invoke("Send", message);
	}
}

サーバー側に:

// OWIN Startup entry point definition
[assembly: OwinStartup(typeof(MyWebApplication.Startup))]

namespace MyWebApplication
{
    // OWIN Startup Code
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // SignalR mapping
            app.MapSignalR();
        }
    }

    // The hub
    public class SampleHub : Hub
    {
        // Hub method
        public void Send(string text)
        {
            // Callback all client's "Receive" method.
            this.Clients.All.Receive(text);
        }
    }
}

Page_Loadedで初期化すべきかどうかはちょっと甘いです(WPアプリの開発経験が浅いので)。正しいと思う方法で実装して下さい。

SgmlReader for Portable Class Library

あけましておめでとうございます。本年もよろしくお願いします。

早速本題。
年始早々取り組んでいる事の依存関係の末端に「SgmlReader」があり、そのままではバージョンとか面倒な事もあって、Portable Class Library化してNuGetで公開しました。

NuGet package ID: SgmlReaderPortable
CodePlex project: SgmlReader for Portable Class Library

ええと、VS2012で作ったので、WP7とかXbox360でも行けるとか(勿論、未確認w)、誰得仕様です。
Portable化のため、外部リソースへのHTTP接続とか出来ないので、デリゲートで外部から依存注入出来るようにしています。

超簡単な例:

// Open stream
using (var stream = new FileStream("target.html", FileMode.Open, FileAccess.Read, FileShare.Read))
{
	var tr = new StreamReader(stream, Encoding.UTF8);

	// Setup SgmlReader
	var sgmlReader = new SgmlReader(tr)
		{
			DocType = "HTML",				// Use internal HTML4 DTD (No external access)
			WhitespaceHandling = true,		// Means WhitespaceHandling.All
			CaseFolding = CaseFolding.ToLower
		};

	// create document
	var document = new XmlDocument();
	document.PreserveWhitespace = true;
	document.XmlResolver = null;
	document.Load(sgmlReader);
}

外部リソースにアクセス可能にする例:

// Open stream
using (var stream = new FileStream("target.sgml", FileMode.Open, FileAccess.Read, FileShare.Read))
{
	var tr = new StreamReader(stream, Encoding.UTF8);

	// Define base uri
	var baseUri = new Uri("http://www.example.com/");

	// Setup SgmlReader
	var sgmlReader = new SgmlReader(tr, baseUri,

		// Stream opener delegate (Separate physical resource access)
		uri => new StreamInformation
			{
				Stream = WebRequest.Create(uri).GetResponse().GetResponseStream(),
				DefaultEncoding = Encoding.UTF8
			})

		{
			WhitespaceHandling = true,
			CaseFolding = CaseFolding.ToLower
		};

	// create document
	var document = new XmlDocument();
	document.PreserveWhitespace = true;
	document.XmlResolver = null;
	document.Load(sgmlReader);
}

取りあえず、一つ依存が片付いた…

Advent LINQ 2013 : LINQ連載の目次

丁度区切りがついたので、目次を付けておきます


Advent LINQ (1): SplitAsEnumerable
Advent LINQ (2): TextReader.AsEnumerable
Advent LINQ (3): FilesAsEnumerable
Advent LINQ (4): Buffering
Advent LINQ (5): Flatten
Advent LINQ (6): Match
Advent LINQ (7): Count
Advent LINQ (8): ElementAt
Advent LINQ (9): ParallelQuery対応拡張メソッドの実装
Advent LINQ (10): .NET 2.0でLINQを使う
Advent LINQ (11): SQL文を取得する
Advent LINQ (12): リファクタリング可能なメンバ名
Advent LINQ (13): ExpressionとLINQ
Advent LINQ (14): 条件式が実行される場所
Advent LINQ (15): SQL表現可能な式
Advent LINQ (16): 表形式と構造化形式
Advent LINQ (17) : Expressionの評価
Advent LINQ (18) : Expressionの探索
Advent LINQ (19) : Expressionのコンパイルと独立性
Advent LINQ (20) : IEnumerableとIQueryableの相互変換
Advent LINQ (21) : LINQプロバイダー
Advent LINQ (22) : 解釈するクエリの範囲
Advent LINQ (23) : 事前評価可能な式
Advent LINQ (24) : The Provider Core
Advent LINQ (25) : LINQプロバイダーの仕上げ

Advent LINQ 2013 : あとがき

Advent LINQ 2013 : おまけ (Parallel.ForEach)


以前の連載:

LINQは本当に強力だ (1) データ加工の究極の道具
LINQは本当に強力だ (2) クエリの連結と拡張メソッド
LINQは本当に強力だ (3) 拡張メソッドによる拡張
LINQは本当に強力だ (4) アルゴリズムヘルパーメソッド
LINQは本当に強力だ (5) クエリ構文とメソッド構文
LINQは本当に強力だ (6) TextFieldContext
LINQは本当に強力だ (7) 範囲変数と構文の選択
LINQは本当に強力だ (8) 何が並列化されるのか

Advent LINQ 2013 : おまけ (Parallel.ForEach)

Advent LINQ (10): .NET 2.0でLINQを使うの回で、比較的簡単にParallel.ForEachを自前で実装出来ると書いたので、例を見せる。
 
 

namespace System.Threading.Tasks
{
    public static class Parallel
    {
        public static void ForEach<T>(IEnumerable<T> enumerable, Action<T> action)
        {
            Debug.Assert(enumerable != null);
            Debug.Assert(action != null);

            var exceptionList = new List<Exception>();
            using (var wait = new ManualResetEvent(false))
            {
                var count = 1;
                foreach (var item in enumerable)
                {
                    Interlocked.Increment(ref count);

                    ThreadPool.QueueUserWorkItem(new WaitCallback(p =>
                    {
                        try
                        {
                            try
                            {
                                action(item);
                            }
                            catch (Exception ex)
                            {
                                lock (exceptionList)
                                {
                                    exceptionList.Add(ex);
                                }
                            }
                        }
                        finally
                        {
                            if (Interlocked.Decrement(ref count) == 0)
                            {
                                wait.Set();
                            }
                        }
                    }));
                }

                if (Interlocked.Decrement(ref count) == 0)
                {
                    wait.Set();
                }

                wait.WaitOne();
            }

            if (exceptionList.Count >= 1)
            {
                throw new AggregateException(exceptionList);
            }
        }

        public static void Invoke(params Action[] actions)
        {
            ForEach(actions, action => action());
        }
    }
}

コンパイルを通すには、他にもActionクラスが必要だ。このサンプルはVSCoverageToEmmaで実装したもので、以下の場所に完全なコードがある。

VSCoverageToEmma/VSCoverageToEmma.Core/Tasks/Parallel.cs

Advent LINQ 2013 : あとがき

むちゃくちゃ大変でした。

思い立ったのは11月の下旬、何だか周りでAdvent Calendarのひそひそ声が聞こえ始めた頃です。Advent Calendarをやった事が無かったので、「ふむ、25日まで1日ずつネタを書けばいいのか、簡単じゃないか。やってみるか、一人で」などと考えたのが悲劇の始まりだった。

まず、記事のさじ加減が分からない。「まぁ、LINQなら何でもネタがあるよね。そうだ、縛りを入れるか。一つ一つの記事がある程度独立していて、かつ、短く、読者が応用を想像できるような内容にするってのはどうだろうか?」

LINQは現場で、「当たり前に使っている」か、「全く使っていないか」の両極端に感じる。twitterで流れてくるものの中には、「LINQ禁止」とか、あり得ない現場もあるようだ。
そんなわけで、あんまり専門寄りに寄ってしまってもあれだから、少しでも底辺を持ち上げて、「LINQって何?」な人口を減らす事が出来たら良いなと思った。
(まあ、これは前の連載でも意識していたけど)

で、書き始めたのだが… すぐに壁にぶち当たった。

既に自分の中ではLINQが「空気」状態だった。「空気について説明する」が如く、フォーカスする対象が無いのだ。例えば、きわめて具体的なシチュエーションであれば、いくらでもLINQによる解決策を提示できる。しかし、具体例を示し過ぎると、読者が応用を想像しにくくなってしまう。あくまで、ネタの背景が分かるようにし、かつサジ加減を調整してあまり具体的過ぎる例にしないように(時にはわざとアホな方に振ったり)する必要があった。そして、それに耐えうるネタでなければならない…

10回目ぐらいまではパパッと思いついたネタも、その後が続かない。初めはばりばり書いて書き溜めておいたので良かったのだが、本業が忙しいこともあり、段々ストックが底をついてくる。金を貰っているわけでもないのにこのプレッシャー!!

そして、ふらふらと、やる気の無かったIQueryableネタをやり始めた。もうヤバい。何がって、この後は、アレだ。禁断のネタ「LINQプロバイダー」しかないじゃないか。

LINQプロバイダーなら、このネタだけで最後まで続けられるという点では、ネタ詰まりは回避できる。ただ、LINQプロバイダーは複雑すぎて、色々説明した後でないと、本題に入れない。これでは、1話毎に完結させるという縛りを満たすことがとても難しい。おまけに複雑さを低減するために、どうしても図を書かなければならない。

この迷い、15回ぐらいまで続く。少し読み返してみると、苦悩ぶりが伺えるw

さらに、NGK2013BでLT登壇したあおりを受け、あろうことか、JenkinsのAdvent Calendarにポチっとしてしまった… この状態で気がふれたとしか思えない。何故書く気になったのか、未だに良く分からない…

そして、ネタ考案の時間切れと半分ヤケとで、LINQプロバイダーをやらざるを得なくなり、腹を括って突入。書くからには、LINQ to SQLやLINQ to Entitiesがブラックボックス過ぎて理解不能と思っている方に向けて、少しでも「仕掛け」が見えるようにする必要があるだろうと。

AdventCalendarそんなわけで、後半は特に検証とかやったつもりですが、間違いがあったらゴメンナサイ。頑張って、強く縛りを意識して書いてみました。

右側のカレンダーを見ると、すげーなー、我ながら良く書いたよなぁ、と思います。
本日は23日の夜中、とりあえず、風呂入って寝ますw

Happy Merry Christmas!!