30

以下は、Castle Dynamic ProxyライブラリInterceptを実装するカスタム型のメソッドのコードです。このスニペットは、ここに投稿されているAOPベースのロギング概念実証コンソール アプリからのものです。IInterceptor

    public void Intercept(IInvocation invocation)
    {
        if (Log.IsDebugEnabled) Log.Debug(CreateInvocationLogString("Called", invocation));
        try
        {
            invocation.Proceed();
            if (Log.IsDebugEnabled)
                if (invocation.Method.ReturnType != typeof(void))
                    Log.Debug("Returning with: " + invocation.ReturnValue);
        }
        catch (Exception ex)
        {
            if (Log.IsErrorEnabled) Log.Error(CreateInvocationLogString("ERROR", invocation), ex);
            throw;
        }
    }

これは、通常のメソッド呼び出しでは期待どおりに機能しますが、メソッドで試した場合( C# 5.0asyncのキーワードを使用) には機能しません。async/awaitそして、私はこの背後にある理由も理解していると信じています.

が機能するために、コンパイラはメソッドの機能本体をバックグラウンドでステート マシンに追加し、同期的に完了できないasync/await最初の式が検出されるとすぐに、制御が呼び出し元に戻ります。awaitable

asyncまた、戻り値の型を調べて、次のようなメソッドを扱っているかどうかを判断できます。

            if (invocation.Method.ReturnType == typeof(Task) || 
                (invocation.Method.ReturnType.IsGenericType && 
                 invocation.Method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)))
                Log.Info("Asynchronous method found...");

これは、どちらかasyncを返すかどうかを返すメソッドでのみ機能しますが、私はそれで問題ありません。TaskTask<>void

が元の呼び出し元ではなくそこに戻るようにするには、Interceptメソッド内でどのような変更を加える必要がありますか?awaiter

4

8 に答える 8

20

ジョンの答えのおかげで、これは私が最終的に得たものです:

public void Intercept(IInvocation invocation)
{
    if (Log.IsDebugEnabled) Log.Debug(CreateInvocationLogString("Called", invocation));
    try
    {
        invocation.Proceed();

        if (Log.IsDebugEnabled)
        {
            var returnType = invocation.Method.ReturnType;
            if (returnType != typeof(void))
            {
                var returnValue = invocation.ReturnValue;
                if (returnType == typeof(Task))
                {
                    Log.Debug("Returning with a task.");
                }
                else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
                {
                    Log.Debug("Returning with a generic task.");
                    var task = (Task)returnValue;
                    task.ContinueWith((antecedent) =>
                                          {
                                              var taskDescriptor = CreateInvocationLogString("Task from", invocation);
                                              var result =
                                                  antecedent.GetType()
                                                            .GetProperty("Result")
                                                            .GetValue(antecedent, null);
                                              Log.Debug(taskDescriptor + " returning with: " + result);
                                          });
                }
                else
                {
                    Log.Debug("Returning with: " + returnValue);
                }
            }
        }
    }
    catch (Exception ex)
    {
        if (Log.IsErrorEnabled) Log.Error(CreateInvocationLogString("ERROR", invocation), ex);
        throw;
    }
}
于 2013-01-12T00:22:36.887 に答える
20

おそらく「問題」は、タスクを返していることをログに記録しているだけであり、そのタスク内のが必要ですか?

その場合でも、タスクが完了するのを待たずに、すぐに呼び出し元にタスクを返す必要があります。それを破ると、根本的に物事が台無しになります。

ただし、タスクを呼び出し元に返す前に、タスクが完了したときにTask.ContinueWith結果 (または失敗) を記録する継続を ( 経由で) 追加する必要があります。それでも結果情報が得られますが、もちろん、他のログ記録の後に記録する可能性があります。戻る直前にログを記録することもできます。これにより、次のようなログが生成されます。

Called FooAsync
Returned from FooAsync with a task
Task from FooAsync completed, with return value 5

タスクから結果を取得する作業 (正常に完了した場合) は、リフレクションを使用して行う必要がありますが、これは少し面倒です。または、動的型付けを使用することもできます。(どちらにしても、パフォーマンスが少し低下します。)

于 2013-01-11T23:14:29.887 に答える
8

次の一般的でクリーンなソリューションで明確にしようとしています。

  • 継続タスクとしてカスタム コードを追加するインターセプトasyncメソッド。

最良の解決策は、キーワードを使用してコンパイラの型チェックをバイパスし、実行時dynamicに Task と Task の違いを解決することだと思います。<T>

public void Intercept(IInvocation invocation)
{
    invocation.Proceed();
    var method = invocation.MethodInvocationTarget;
    var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
    if (isAsync && typeof(Task).IsAssignableFrom(method.ReturnType))
    {
        invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue);
    }
}

private static async Task InterceptAsync(Task task)
{
    await task.ConfigureAwait(false);
    // do the logging here, as continuation work for Task...
}

private static async Task<T> InterceptAsync<T>(Task<T> task)
{
    T result = await task.ConfigureAwait(false);
    // do the logging here, as continuation work for Task<T>...
    return result;
}
于 2016-09-30T06:25:49.860 に答える
4

私の2セント:

asyncメソッドの場合、インターセプターの目的は、継続を介して、呼び出しによって返されるタスクを「強化」することであることが正しく確立されています。

さて、インターセプターのジョブを完了するために返さなければならないのは、まさにこのタスクの継続です。

したがって、上記の議論と例に基づいて、これは通常のメソッドと「生の」メソッドで完全にうまく機能しasync Taskます。

public virtual void Intercept(IInvocation invocation)
{
    try
    {
        invocation.Proceed();
        var task = invocation.ReturnValue as Task;
        if (task != null)
        {
            invocation.ReturnValue = task.ContinueWith(t => {
                if (t.IsFaulted)
                    OnException(invocation, t.Exception);
            });
        }
    }
    catch (Exception ex)
    {
        OnException(invocation, ex);
    }
}

public virtual void OnException(IInvocation invocation, Exception exception)
{
    ...
}
  1. しかし、メソッドを扱う場合async Task<T>、上記はインターセプトによって返されるタスクのタイプを誤っTask<T>て通常から通常に変更します。Task

  2. 呼び出したいメソッドである ではTask.ContinueWith()なく、 を呼び出していることに注意してください。Task<TResult>.ContinueWith()

これは、最終的にそのようなインターセプトを待っているときに発生する例外です。

System.InvalidCastException: タイプ 'System.Threading.Tasks.ContinuationTaskFromTask' のオブジェクトをタイプ 'System.Threading.Tasks.Task`1 にキャストできません

于 2014-05-08T15:41:32.400 に答える
2

を返すメソッドをインターセプトする必要があるため、プロセスを簡素化Task<TResult>する拡張機能を作成しました。Castle.Core

Castle.Core.AsyncInterceptor

パッケージはNuGetでダウンロードできます。

解決策は主に@silas-reinagelからのこの回答に基づいていますが、 IAsyncInterceptorを実装する新しいインターフェイスを提供することで簡素化されています。インターセプトを実装に似たものにするためのさらなる抽象化もあります。Interceptor

詳細については、プロジェクトのreadmeを参照してください。

于 2017-10-27T11:02:36.593 に答える
0
   void IInterceptor.Intercept(IInvocation invocation) {
       try {
           invocation.Proceed();
           var task = invocation.ReturnValue as Task;
           if (task != null && task.IsFaulted) throw task.Exception;
       }
       catch {
           throw;
       }
   }
于 2016-04-19T20:46:38.387 に答える
-1

それ以外の:

tcs2.SetException(x.Exception);

以下を使用する必要があります。

x.Exception.Handle(ex => { tcs2.SetException(ex); return true; });

本当の例外を泡立てるために...

于 2016-06-24T01:27:59.447 に答える