5

私は似たようなコードを持っています

void ExecuteTraced(Action a, string message)
{
    TraceOpStart(message);
    a();
    TraceOpEnd(message);
}

コールバック (a) は ExecuteTraced を再度呼び出すことができ、場合によっては非同期で (ThreadPool、BeginInvoke、PLINQ などを介して)、操作スコープを明示的にマークすることはできません。ネストされたすべての操作をトレースしたい (非同期で実行されている場合でも)。そのため、論理呼び出しコンテキスト内で最後にトレースされた操作を取得する機能が必要です (同時スレッドが多数存在する可能性があるため、lastTraced 静的フィールドを使用することはできません)。

CallContext.LogicalGetData と CallContext.LogicalSetData がありますが、残念ながら、LogicalCallContext は、EndInvoke() が呼び出されると変更を親コンテキストに反映します。さらに悪いことに、EndInvoke() が非同期で呼び出された場合、これはいつでも発生する可能性があります。 EndInvoke は現在の CallContext を変更します - なぜですか?

また、Trace.CorrelationManager もありますが、CallContext をベースとしており、同じような問題があります。

回避策があります。非同期操作が終了したときに伝達されない CallContext.HostContext プロパティを使用します。また、複製しないため、値は不変である必要があります-問題ではありません。ただし、HttpContext で使用されるため、回避策は Asp.Net アプリでは使用できません。

私が見る唯一の方法は、HostContext (私のものではない場合) または LogicalCallContext 全体を動的にラップし、最後にトレースされた操作の横にすべての呼び出しをディスパッチすることです。

4

1 に答える 1

7

わかりました、私は自分自身に答えています。

短いもの:解決策はありません。

少し詳しく:

問題は、各論理コンテキストごとに最後のアクティブな操作を保存する方法が必要なことです。トレース コードは実行フローを制御できないため、lastStartedOperation をパラメーターとして渡すことはできません。呼び出しコンテキストは (たとえば、別のスレッドが開始された場合) 複製される可能性があるため、コンテキストの複製として値を複製する必要があります。

CallContext.LogicalSetData() は適していますが、非同期操作が終了したときに値を元のコンテキストにマージします (実際には、EndInvoke が呼び出される前に行われたすべての変更を置き換えます)。理論的には、非同期でも発生する可能性があり、CallContext.LogicalGetData() の予測できない結果をもたらします。

asyncCallback 内の単純な呼び出し a.EndInvoke() は元のコンテキストの値を置き換えないため、理論的に言います。ただし、リモート呼び出しの動作は確認していません (WCF は CallContext をまったく尊重していないようです)。また、ドキュメント(古いもの)には次のように書かれています:

BeginInvoke メソッドは、CallContext をサーバーに渡します。EndInvoke メソッドが呼び出されると、CallContext がスレッドにマージされます。これには、BeginInvoke と EndInvoke が順番に呼び出される場合と、BeginInvoke が 1 つのスレッドで呼び出され、EndInvoke がコールバック関数で呼び出される場合が含まれます。

最後のバージョンはそれほど明確ではありません:

BeginInvoke メソッドは、CallContext をサーバーに渡します。EndInvoke メソッドが呼び出されると、CallContext に含まれるデータが、BeginInvoke を呼び出したスレッドにコピーされます。

フレームワークのソースを掘り下げると、現在のスレッドの現在の ExecutionContext 内の LogicalCallContext 内のハッシュテーブル内に値が実際に格納されていることがわかります。

コンテキスト クローンを呼び出すとき (BeginInvoke など)、LogicalCallContext.Clone が呼び出されます。そして、EndInvoke (少なくとも元の CallContext 内で呼び出された場合) は LogicalCallContext.Merge() を呼び出し、m_Datastore 内の古い値を新しい値に置き換えます。

したがって、クローンは作成されるがマージは戻されない値を何らかの形で提供する必要があります。

LogicalCallContext.Clone() は、m_RemotingData と m_SecurityData の 2 つのプライベート フィールドのコンテンツも (マージせずに) 複製します。フィールドのタイプが内部として定義されているため、それらから派生することはできず (発行しても)、プロパティ MyNoFlowbackValue を追加し、m_RemotingData (または別のフィールド) フィールドの値を派生クラスのインスタンスに置き換えます。

また、フィールドの型は MBR から派生していないため、透過プロキシを使用してそれらをラップすることはできません。

LogicalCallContext から継承できませんでした - 封印されています。(実際には、CLR プロファイリング API を使用して、モック フレームワークのように IL を置き換えることができます。望ましい解決策ではありません。)

LogicalCallContext はハッシュテーブル自体ではなく、ハッシュテーブルのコンテンツのみをシリアル化するため、m_Datastore 値を置き換えることができませんでした。

最後の解決策は、CallContext.HostContext を使用することです。これにより、LogicalCallContext の m_hostContext フィールドにデータが効果的に格納されます。LogicalCallContext.Clone() は m_hostContext の値を (クローンではなく) 共有するため、値は不変でなければなりません。問題ありません。

また、HttpContext を使用すると、古い値を置き換える CallContext.HostContext プロパティが設定されるため、これでも失敗します。皮肉なことに、HttpContext は ILogicalThreadAffinative を実装していないため、m_hostContext フィールドの値として格納されません。古い値をnullに置き換えるだけです。

したがって、CallContext はリモーティングの一部であり、リモーティングは廃止されているため、解決策はありません。

PS Thace.CorrelationManager は内部で CallContext を使用するため、期待どおりに動作しません。ところで、LogicalCallContext には、コンテキストの複製で CorrelationManager の操作スタックを複製するための特別な回避策があります。残念ながら、マージに関する特別な回避策はありません。完全!

PPS サンプル:

static void Main(string[] args)
{
    string key = "aaa";
    EventWaitHandle asyncStarted = new AutoResetEvent(false);
    IAsyncResult r = null;

    CallContext.LogicalSetData(key, "Root - op 0");
    Console.WriteLine("Initial: {0}", CallContext.LogicalGetData(key));

    Action a = () =>
    {
        CallContext.LogicalSetData(key, "Async - op 0");
        asyncStarted.Set();
    };
    r = a.BeginInvoke(null, null);

    asyncStarted.WaitOne();
    Console.WriteLine("AsyncOp started: {0}", CallContext.LogicalGetData(key));

    CallContext.LogicalSetData(key, "Root - op 1");
    Console.WriteLine("Current changed: {0}", CallContext.LogicalGetData(key));

    a.EndInvoke(r);
    Console.WriteLine("Async ended: {0}", CallContext.LogicalGetData(key));

    Console.ReadKey();
}
于 2010-04-21T11:59:23.393 に答える