3

ログ用にコンソール出力ストリームをキャプチャするために、コンソール出力ストリームをインターセプトする必要がありますが、アプリケーションが正しく動作するように、元のストリームに物事を渡します。これは明らかに、元のConsole.OutTextWriterを。で変更する前に保存することを意味しConsole.SetOut(new MyTextWriterClass(originalOut))ます。

Outプロパティを取得し、SetOut()メソッドを呼び出す個々の操作はConsole、スレッドセーフな方法で実装されていると思います。ただし、他のスレッド(たとえば、制御できず、変更を期待できないクライアントアプリケーションコードを実行しているため、独自のカスタムロックスキームに依存できない)ができないことを確認したいと思います。 getとsetの間に誤って変更し、変更によって上書きされてしまいます(アプリケーションの動作が壊れます!)。他のコードは単にSetOut()を呼び出す可能性があるため、私のコードは理想的にConsoleは(1つあると仮定して)内部で使用されるのと同じロックを取得する必要があります。

残念ながら、Consoleこれは(静的)クラスであり、インスタンスではないため、単にlock (Console)。また、クラスのドキュメントを見ると、ロックについては何も言及されていないようです。これは、これらのコンソールメソッドの通常予想される使用法ではありませんが、アトミック操作としてこれを行うための安全な方法があるはずです。

標準のロックスキームに失敗しましたが、これを確実にする他の方法はありますか?このような短いクリティカルセクション(および1回だけ実行)の場合、それが唯一の方法である場合は、他のすべてのスレッドを一時的にブロックすることも許容される場合があります。C#と.NET2.0を使用しています。

それでも(クライアントアプリケーションを中断せずに)可能でない場合は、クライアントアプリケーションがコンソール出力リダイレクトし、get操作とset操作の間にたまたまそれを実行する可能性が非常に低いことに依存する必要があります。万が一に備えて、すべての拠点をカバーしたいと思います。

編集:サンプルコードを使用した具体的な回答が得られたので、質問のタイトルを書き直して、回答が役立つユースケースをより一般的に反映し、より明確にしました。また、「アトミック」のタグを追加しました。

4

4 に答える 4

2

SetOut の実装を見ると、スレッドセーフに見えます。

[HostProtection(SecurityAction.LinkDemand, UI=true)]
public static void SetOut(TextWriter newOut)
{
    if (newOut == null)
    {
        throw new ArgumentNullException("newOut");
    }
    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
    _wasOutRedirected = true;
    newOut = TextWriter.Synchronized(newOut);
    lock (InternalSyncObject)
    {
        _out = newOut;
    }
}

編集

私が思いつく解決策に最も近いのは、リフレクションを使用して InternalSyncObject を取得し、それをロックすることです。

注意してください。これは非常に悪い考えであり、他に選択肢がない場合にのみ使用してください。フレームワークが予期せぬ動作をし、プロセスがクラッシュする可能性があります。

また、内部変数がまだ使用されていることを確認して、サービス パックとメジャー リリースにも注意を払う必要があります。内部的なものであるため、次のリリースで実装されるという保証はありません。リフレクションを使用しない場合は、コードを防御的に記述し、ユーザーエクスペリエンスを適切に低下させてください。

幸運を:-)

于 2008-12-15T14:46:56.467 に答える
1

さて、Josh が提案したリフレクション アプローチの実装を実際に作成する時間ができました。基本的なコード (ConsoleIntercepterから継承する私のクラスTextWriter) は次のとおりです。

private static object GetConsoleLockObject()
{
    object lockObject;
    try
    {
        const BindingFlags bindingFlags = BindingFlags.GetProperty |
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
        // It's currently private, but we'd be happy if it were public, too.
        Type consoleType = typeof(Console);

        lockObject = consoleType.InvokeMember("InternalSyncObject", bindingFlags,
                                              null, null, null);
    }
    catch
    {
        lockObject = null; // Return null on any failure.
    }
    return lockObject;
}
public static void RegisterConsoleIntercepter()
{
    object lockObject = GetConsoleLockObject();
    if (lockObject != null)
    {
        // Great!  We can make sure any other changes happen before we read
        // or after we've written, making this an atomic replacement operation.
        lock (lockObject)
        {
            DoIntercepterRegistration();
        }
    }
    else
    {
        // Couldn't get the lock object, but we still need to work, so
        // just do it without an outer lock, and keep your fingers crossed.
        DoIntercepterRegistration();
    }
}

次に、次のDoIntercepterRegistration()ようになります。

private static void DoIntercepterRegistration()
{
    Console.SetOut(new ConsoleIntercepter(Console.Out));
    Console.SetError(new ConsoleIntercepter(Console.Error));
}

これは、Out と Error をロックで保護されたアトミックに置き換えるためのものです。明らかに、実際の傍受、処理、および前の s への受け渡しにTextWriterは追加のコードが必要ですが、それはこの質問の一部ではありません。

(.NET2.0 用に提供されたソース コードでは) Console クラスは、InternalSyncObject を使用して Out と Error の初期化を保護し、SetOut() と SetError() を保護しますが、Out と Error の読み取りに関するロックは使用しません。それらが以前に初期化されたら。これは私の特定のケースでは十分ですが、もっと複雑な(そしてクレイジーな)ことをした場合、原子性違反が発生する可能性があります。その問題に関して有用なシナリオは思いつきませんが、意図的に病的なシナリオは想像できます。

于 2009-02-11T20:28:01.623 に答える
0

In general, I would use the security model to keep client code from redirecting the Console while you're not looking. I'm sure that requires Full Trust, so if client code is running with Partial trust, it won't be able to call SetOut().

于 2009-01-13T18:54:14.633 に答える
0

早い段階でこれを行うことがMain()できれば、競合状態を回避する可能性がはるかに高くなります。特に、何かがワーカー スレッドを作成する前に行うことができれば可能です。または、いくつかのクラスの静的コンストラクターで。

Main()属性でマークされている場合[STAThread]、シングル スレッド アパートメントで実行されるため、それも役立ちます。

于 2008-12-15T03:50:03.843 に答える