Visual Studio .NETスタートブック
サポートページ
補足情報
7章 P.128における補足説明Fire-and-Forgetの仕組みは,デリゲートのBeginInvokeメソッドを呼び出した後にそれを放置し,EndInvokeメソッドを呼び出さないことで成り立つメカニズムですが,本書の執筆後,先日よりダウンロード可能になっている.NET Framework SDKVersion1.1のドキュメントを確認したところ,EndInvokeは必ず呼び出さなければならない旨の記述[1]があることを確認しました。
従って,本書に記載されている「Fire-and-Forgetのパターン」(P128のリスト14)は,そのまま適用するには不適切なコードである可能性があります。本書は.NETFramework Version 1.0をベースに書かれています。1.0の当時は,ドキュメントにはこのような記述はありませんでした。しかし,残念ながらこれが「1.0では問題ないが,1.1では問題になる」というケースなのか,それとも「1.0のときにドキュメントに記述し忘れた」ケースなのかどうかはわかりません。
安全を取るならば,Version 1.1のドキュメントに記述されているとおり,BeginInvokeメソッドを呼び出した場合は,EndInvokeメソッドを呼び出したほうがいいでしょう。それでは「Fire-and-Forgetパターン」は実現不可能かといえばそうではありません。米国DevelopMentor社が主催するメーリングリストに,DevelopMentor社のインストラクタであり,Win32のスレッディングに関する権威であるMikeWoodring氏が投稿したサンプルコードが掲載されています(以下の[2][3]参照)。参考までに,文末に該当部分の投稿の日本語訳を掲載します。なお,投稿の翻訳,およびサンプルコードの掲載に関しては,Mike Woodring氏に直接許可をいただいております。
なお,本コードおよび本件に関わるご質問やご意見に関しては,Woodring氏に直接送らずに,いったん本書の質問窓口にご送信ください。筆者がWoodring氏に送信することで了解を受けております。どうぞよろしくお願いします。
[2] http://discuss.develop.com/archives/wa.exe?A2=ind0302B&L=ADVANCED-DOTNET&P=R1085&I=-3
[3] http://discuss.develop.com/archives/wa.exe?A2=ind0302B&L=ADVANCED-DOTNET&D=0&I=-3&P=6015
── [2]の投稿の翻訳 ──
撤回しなければならないことがあります。ランタイムチームが,EndInvokeが呼び出されない場合は,メモリをリークする権限を担保すると言っていることがわかりました。何がどうなっているのかを調べてみたところ,1.1バージョンのフレームワークのSDKドキュメントに次の記述を見つけました。
"CAUTION Always call EndInvoke after your asynchronous call
completes."
「警告 非同期呼び出しが完了したら,必ずEndInvokeを呼び出すこと」
私の知る限り,彼らがこんなことを言ったのはこれが初めてです。そして,この内容はBox氏やGrimes氏が彼らの著書の中で触れているのは,これに関する内容とは対照的な内容です。
このため,fire-and-forgetのセマンティクスは,自分の手で実現しなければなりません。例えば次のようなコードが考えられるでしょう。
どうやらこの件に関してはまだ内部的に議論されているところのようですから,彼らがこのバージョン1.1のドキュメントに記述した警告文を撤回する可能性もないわけではありませんが,現時点では低い可能性と言わざるを得ません。
-Mike
DevelopMentor
http://staff.develop.com/woodring
──上記[4]のサンプルコード(抜粋) ──
// AsyncHelper // // このクラスのFireAndForgetメソッドを利用すると,任意のメソッドを // 非同期的に呼び出したときに,戻り値が戻ってくるのを待つ必要がなくなります。 // // このクラスは次のようなコードで利用します。 // この例では,「void SomeMethod(string, double)」という形態のメソッドが // 定義されていると仮定しています。 // // SomeDelegate sd = new SomeDelegate(SomeMethod); // AsyncHelper.FireAndForget(sd, "pi", 3.1415927); // // 注意 - 次のコードには不要と思われる段階が1つ余計に含まれています。 // コードの目標はBeginInvokeを使ってDelegate.DynamicInvokeを呼び出すことです。 // BeginInvokeによって非同期性が得られ,DynamicInvokeを使うことで // あらゆるデリゲートに適用できるようになります。ですが, // DynamicInvokeを直接呼び出してみると,実行エンジンに関する致命的なエラーが // 出てしまいました。BeginInvokeでシムメソッドを呼び出し,そこからDynamicInvoke // メソッドを呼び出すとうまくいきました。変ですね(1.0でも1.1でも同様の動作です)。 public class AsyncHelper { delegate void DynamicInvokeShimProc( Delegate d, object[] args ); static DynamicInvokeShimProc dynamicInvokeShim = new DynamicInvokeShimProc(DynamicInvokeShim); static AsyncCallback dynamicInvokeDone = new AsyncCallback(DynamicInvokeDone); public static void FireAndForget( Delegate d, params object[] args ) { dynamicInvokeShim.BeginInvoke(d, args, dynamicInvokeDone, null); } static void DynamicInvokeShim( Delegate d, object[] args ) { d.DynamicInvoke(args); } static void DynamicInvokeDone( IAsyncResult ar ) { dynamicInvokeShim.EndInvoke(ar); } } |
お詫びと訂正(正誤表)
本書掲載情報に誤記がありましたので訂正させていただきます。
読者の皆様および関係各位にご迷惑をおかけしたことを,深くお詫び申し上げます。
■ P.39
誤 |
図27にて,CmdShowCasheDateからの引き出し線が「ファイルからのキャッシュの読み込み」を指している |
正 |
図27にて,CmdShowCasheDateからの引き出し線は「キャッシュデータからの表示」を指す |
■ P.40 CasheDependency
本文中にて
先ほどのサンプルを変更して依存関係の設定を追加します。 |
とありますが,この設定方法が欠落していました。
以下のようにリスト26 データをロードするコードを書き換えます。
System.EventArgs) Handles cmdCacheLoad.Click Dim dsXML As DataSet = New DataSet() dsXML.ReadXml(MapPath("sampleData.xml")) Cache.Insert("cacheData", dsXML, New System.Web.Caching.CacheDependency(MapPath("sampleData.xml"))) Label1.Text = "完了" End Sub |