トップ «前の日記(2015-10-11) 最新 次の日記(2015-10-13)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2015-10-12

_ C#のGC.KeepAliveの謎

ちょっとSystem.Threading.Timerを使っていろいろ実験しているのだが解せない(追記:猪俣さんに、お前のドキュメントの誤読と指摘されたので、解せなくなくなった)。

以下は期待通りに3秒後にHelloが表示される。変数tの参照箇所はGC.Collectをまたいでいるから、t(その実体はTimerのインスタンス)が保持されるのは当然だ。

using System;
using System.Threading;
class TimerTest
{
    static void Main()
    {
        var t = new Timer(state => Console.WriteLine("Hello!"),
                          null, 3000, Timeout.Infinite);
        GC.Collect();
        Console.Read();
        t.Dispose();
    }
}

で、Timer.KeepAliveの説明を読むと、以後の参照が無くても呼び出したメソッドの中では生存しているように読める。

追記:そうではなく、そのメソッドの先頭から、Timer.KeepAliveの呼び出し時点まで、生存させるという意味らしい。(なんか意味ないメソッドのような気がするが、他に参照を記述しようがないオブジェクトに対して、コード中に参照を明示するためのメソッドと解釈するのが妥当なようだ。(さらに追記:本当に何もしていない。というかコメントにthisポインタについて書いてあるけど味わい深い、のあたりをおそらく藤原さんが指摘している))。

が、以下はHelloが表示されない。

using System;
using System.Threading;
class TimerTest
{
    static void Main()
    {
        var t = new Timer(state => Console.WriteLine("Hello!"),
                          null, 3000, Timeout.Infinite);
        GC.KeepAlive(t);
        GC.Collect();
        Console.Read();
    }
}

追記:当然、GC.KeepAliveの呼び出し後にGC.Collectを呼んでいるからその時点で回収されるのは正しいことになる。

GC.CollectはKeepAliveして保持したオブジェクトも強制回収するのだろうか? でも、それっておかしくはないか? (追記:したがっておかしくない。KeepAliveはオブジェクトを保持する機構ではなく、生存期間をマークするためのメソッド。以降の段落以降も当然、誤解したまま書いているので当然の動作を検証しているだけ)

本来の用途からはCOMオブジェクトや他のアンマネージドコードにオブジェクトを与えたために、C#のソースコード上は参照が無いように見える場合に、KeepAliveで確保するのだから、当然、他のスレッドなどがGC.Collectを呼んだ場合にも生存は保証されなければおかしい。

それとも、これは同一スレッドだからかな?

で、GC.Collectを別スレッドで実行してみる。

using System;
using System.Threading;
class TimerTest
{
    static void Main()
    {
        var t = new Timer(state => Console.WriteLine("Hello!"),
                          null, 3000, Timeout.Infinite);
        GC.KeepAlive(t);
        ThreadPool.QueueUserWorkItem(new WaitCallback(s => GC.Collect()));
        Console.Read();
    }
}

やっぱり表示されない(=Timerインスタンスはゴミになっている)。

.NET Framework 2.0ならどうかな? とコンパイルしようとしたら当然のようにラムダ式がエラーになって面倒だからやめた。

GC.Collectをアプリケーション内で呼ぶのが間違いで(というのは一理くらいある)、KeepAliveはGC.Collectに勝てないということがドキュメントされているなら納得もするのだが、GC.KeepAliveの説明やGC.Collectの説明には書かれていないんだよなぁ。

追記:なんだか読みにくい文章だなぁと感じたら、多分、誤読を疑うべき(人は都合が良いように解釈しがちだからだ)。というのが教訓。

This method references the obj parameter, making that object ineligible for garbage collection from the start of the routine to the point, in execution order, where this method is called. Code this method at the end, not the beginning, of the range of instructions where obj must be available.

追記:結論としては、Disposableなんだから最初の例のようにDisposeをちゃんと書けばOK(タイマーコールバック内で書いたらだめだけど)。というか、通常の利用方法としてはインスタンス変数に格納すれば良いということになるのだろうな(保持するオブジェクトの生存期間以内で有効なのであれば)。

_ Javaのラムダ式が利用しにくい原因

インターフェイスの定義によるドキュメント性が無くなるのを避けるとインターフェイスをいちいち定義しなければならず、それはよろしくないと書いたことがある

それに対してさくらばさんやきしださんが、java.util.functionに定義されている汎用インターフェイスを使えばいいじゃんと教えてくれたのだが、まったくぴんと来ない。

それはそれとして最近、C#でプログラムを書きまくっていて、ふと気づくと、ガンガンActionやFunctionを使いまくっている(内部処理用ばかりなのでXmlDocumentの記述の必要性がないというのをおいておくとしても)。

が、まったく気にならない。

そこでやっと気づいた。

チェック例外のスローをスルーできないからだ。大体、ラムダ式の中で例外が起きたって、元々の呼び出し元でキャッチすれば良いわけだが、java.util.functionの既定の関数インターフェイス群はthrows Exceptionで定義されていない。

あまりのばかばかしさに、しょうがないのでthrows Exceptionをばかすか付けた関数インターフェイスを定義することになる。

どうせ定義するなら、ドキュメント性を持たせようとしているうちに、元の問題(チェック例外)のことがすっかり念頭から落ちていたようだ。何しろ今や、Javaでメソッドを定義するときは、無意識のうちに引数リストの閉じかっこを打つと同時に空白throws Exception {と書ける境地に到達しているからな。

というわけで、C#は最高ですな。

本日のツッコミ(全3件) [ツッコミを入れる]
_ egtra (2015-10-14 01:10)

「以後の参照が無くても呼び出したメソッドの中では生存しているように読める」が偽なのではないでしょうか?<br><br>https://msdn.microsoft.com/ja-jp/library/system.gc.keepalive%28v=vs.100%29.aspx<br>日本語訳でも「このメソッドが呼び出される時点までの間」という表現が2回現れています。

_ egtra (2015-10-14 01:12)

あと、Visual Studioでプロジェクトを作ってコードを書けば、.NET Framework 2.0ターゲットにしつつラムダ式など(.NETに依存しないC#コンパイラで完結する機能)が使えます。

_ arton (2015-10-14 01:44)

どうもありがとうございます。VisualStudioでC#バージョンと.NETバージョンを別に扱えるのは知らなかったです。<br>最初のツッコミは、ご指摘の通りです(というか、追記でその部分を否定したつもりでした)。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|

ジェズイットを見習え