简体   繁体   中英

using statement with asynchronous Task inside not await

  1. I created an IDisposable object that log the execution time on Dispose.

  2. Then, I created a RealProxy that uses this object to log the time of any method call of my business class

  3. My business class uses async methods, which then leave the using statement before completion of the task, and then log wrong execution time.

Here's my code:

/// <summary>
///  Logging proxy with stopwatch
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class LoggingProxy<T> : RealProxy// where T : MarshalByRefObject
{
    private ILog _logger;
    private readonly T _instance;

    private LoggingProxy(T instance, ILog logger)
        : base(typeof(T))
    {
        _logger = logger;
        _instance = instance;
    }

    /// <summary>
    /// Create the Transparent proy for T
    /// </summary>
    /// <param name="type">An implementation type of T</param>
    /// <returns>T instance</returns>
    public static T Create(ILog logger)
    {
            logger.DebugFormat("[{0}] Instantiate {1}", "LoggingProxy", typeof(T).Name);
            var instance = (T)Activator.CreateInstance(typeof(T), logger);

            //return the proxy with execution timing if debug enable in logger
            if (logger.IsDebugEnabled)
                return (T)new LoggingProxy<T>(instance, logger).GetTransparentProxy();
            else
                return instance;
    }


    /// <summary>
    /// Invoke the logging method using Stopwatch to log execution time
    /// </summary>
    /// <param name="msg"></param>
    /// <returns></returns>
    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = (IMethodCallMessage)msg;
        var method = (MethodInfo)methodCall.MethodBase;

        string methodName = method.Name;
        string className = method.DeclaringType.Name;

        //return directly methods inherited from Object
        if (method.DeclaringType.Name.Equals("Object"))
        {
            var result = method.Invoke(_instance, methodCall.Args);
            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }

        using (var logContext = _logger.DebugTiming("[{0}] Execution time for {1}", className, methodName))
        {
            _logger.DebugFormat("[{0}] Call method {1}", className, methodName);
            //execute the method
            //var result = method.Invoke(_instance, methodCall.Args);
            object[] arg = methodCall.Args.Clone() as object[];
            var result = method.Invoke(_instance, arg);

            //wait the task ends before log the timing
            if (result is Task)
                (result as Task).Wait();

            return new ReturnMessage(result, arg, 0, methodCall.LogicalCallContext, methodCall);
        }

    }

The _logger.DebugTiming method start a stopwatch tand log it on Dispose. The only way I found to make it work with async methods, is to use that line:

            //wait the task ends before log the timing
            if (result is Task)
                (result as Task).Wait();

But I just have the feeling that I break all the benefits of async methods by doing that.

-> If you have a suggestion about how to make a proper implementation

-> Any idea of the real impact of calling wait() on the proxy?

You can use Task.ContinueWith():

public override IMessage Invoke(IMessage msg)
{
    var methodCall = (IMethodCallMessage)msg;
    var method = (MethodInfo)methodCall.MethodBase;

    string methodName = method.Name;
    string className = method.DeclaringType.Name;

    //return directly methods inherited from Object
    if (method.DeclaringType.Name.Equals("Object"))
    {
        var result = method.Invoke(_instance, methodCall.Args);
        return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
    }

    var logContext = _logger.DebugTiming("[{0}] Execution time for {1}", className, methodName);
    bool disposeLogContext = true;
    try
    {
        _logger.DebugFormat("[{0}] Call method {1}", className, methodName);
        //execute the method
        //var result = method.Invoke(_instance, methodCall.Args);
        object[] arg = methodCall.Args.Clone() as object[];
        var result = method.Invoke(_instance, arg);

        //wait the task ends before log the timing
        if (result is Task) {
            disposeLogContext = false;
            ((Task)result).ContinueWith(() => logContext.Dispose());
        }

        return new ReturnMessage(result, arg, 0, methodCall.LogicalCallContext, methodCall);
    }
    finally
    {
        if (disposeLogContext)
            logContext.Dispose();
    }
}

Don't call Wait() on the task - that changes the behavior and might lead to deadlocks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM