【问题标题】:WCF, async, and a confusion of contextWCF、异步和上下文混淆
【发布时间】:2014-07-10 20:20:55
【问题描述】:

好吧,我本来是要命名这个和上下文问题,但显然标题中不允许使用 question 这个词。

无论如何,问题是:我在 WCF 服务中使用 IErrorHandler 以提供日志记录,而不会弄乱我的所有服务代码。到目前为止,这工作得很好。但是,现在我正在尝试迁移到完全异步的服务,我遇到了call stack being a return stack instead of a causality chain 的问题。

现在,我尝试使用Stephen Cleary's logical call context MyStack,结合NinjectIntercept 扩展..

忍者:

Bind<IThing>().To<Thing>()
    .Intercept()
    .With<SimpleContextGenerator>();

SimpleContextGenerator:

public class SimpleContextGenerator : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        using (MyStack.Push(
                 string.Join(".",
                   invocation.Request.Method.DeclaringType.FullName,
                   invocation.Request.Method.Name)))
        {
            invocation.Proceed();
        }
    }
}

然而,问题是双重的:1) using 在错误实际抛出之前完成,2) 1 甚至无关紧要,因为在我到达 IErrorHandler 时整个上下文都已清除。我可以在MyStack 中注释掉Pop 中的代码,当我在IErrorHandler 中点击ProvideFault 时,CurrentContext.IsEmptytrue

所以,我的问题也分为两部分:

1) 有没有办法将上下文保留到 IErrorHandler 调用?

2) 如果没有,是否有其他方法可以在全球范围内记录是否可以访问上下文的错误?

我正在使用 .NET 4.5、Ninject 3.2 和 DynamicProxy 3.2。

说实话,我很高兴知道异常是在哪里引发的——当前的类和方法足以满足我的目的;不需要完整的堆栈。

编辑:如果我使用IExtension&lt;&gt; 将它放入OperationContext,我可以保留它直到我到达IErrorHandler。但是,我仍然不知道方法何时结束,所以我无法确定异常发生在哪里。

【问题讨论】:

    标签: wcf asynchronous ninject callstack ierrorhandler


    【解决方案1】:

    要以IErrorHandler 中可用的方式跟踪堆栈,请使用IExtension&lt;&gt;

    public class ContextStack : IExtension<OperationContext>
    {
    
        // http://stackoverflow.com/a/1895958/128217
    
        private readonly LinkedList<Frame> _stack;
    
        private ContextStack()
        {
            _stack = new LinkedList<Frame>();
        }
    
        public LinkedList<Frame> Stack
        {
            get { return _stack; }
        }
    
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static readonly object _locker = new object();
        public static ContextStack Current
        {
            get
            {
                ContextStack context = OperationContext.Current.Extensions.Find<ContextStack>();
                if (context == null)
                {
                    lock (_locker)
                    {
                        context = OperationContext.Current.Extensions.Find<ContextStack>();
                        if (context == null)
                        {
                            context = new ContextStack();
                            OperationContext.Current.Extensions.Add(context);
                        }
                    }
                }
                return context;
            }
        }
    
        public IDisposable Push(Frame frame)
        {
            Stack.AddFirst(frame);
            return new PopWhenDisposed(frame, Stack);
        }
    
        public void Attach(OperationContext owner) { }
        public void Detach(OperationContext owner) { }
    
        private sealed class PopWhenDisposed : IDisposable
        {
    
            private bool _disposed;
            private readonly Frame _frame;
            private readonly LinkedList<Frame> _stack;
    
            public PopWhenDisposed(Frame frame, LinkedList<Frame> stack)
            {
                _frame = frame;
                _stack = stack;
            }
    
            public void Dispose()
            {
                if (_disposed)
                {
                    return;
                }
                _stack.Remove(_frame);
                _disposed = true;
            }
    
        }
    
    }
    

    这是正在跟踪的Frame

    public class Frame
    {
    
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly string _type;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly string _method;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly Parameter[] _parameters;
    
        public string Type { get { return _type; } }
        public string Method { get { return _method; } }
        public Parameter[] Parameters { get { return _parameters; } }
    
        public Task Task { get; private set; }
        public Exception Exception { get; private set; }
    
        public Frame(Type type, string method, params Parameter[] parameters)
        {
            _type = type.FullName;
            _method = method;
            _parameters = parameters;
        }
    
        public void SetTask(Task task)
        {
            if (Task != null)
            {
                throw new InvalidOperationException("Task is already set.");
            }
            Task = task;
        }
    
        public void SetException(Exception exception)
        {
            if (Exception != null)
            {
                throw new InvalidOperationException("Exception is already set.");
            }
    
            // Unwrap AggregateExceptions with a single inner exception.
            if (exception is AggregateException && ((AggregateException)exception).InnerExceptions.Count == 1)
            {
                Exception = exception.InnerException;
            }
            else
            {
                Exception = exception;
            }
        }
    
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder(Type);
            sb.Append(".");
            sb.Append(Method);
            sb.Append("(");
            sb.Append(string.Join(", ", (object[])Parameters)); // Needed to pick an overload.
            sb.Append(")");
            return sb.ToString();
        }
    
    }
    

    还有Parameter

    public class Parameter
    {
    
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly string _name;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly string _type;
    
        public string Name { get { return _name; } }
        public string Type { get { return _type; } }
    
        public Parameter(string name, Type type)
        {
            _name = name;
            _type = type.Name;
        }
    
        public override string ToString()
        {
            return string.Format("{0} {1}", Type, Name);
        }
    
    }
    

    现在,您想使用此SimpleContextGenerator 管理堆栈:

    public class SimpleContextGenerator : IInterceptor
    {
    
        public void Intercept(IInvocation invocation)
        {
            OperationContextSynchronizationContext synchronizationContext = null;
            try
            {
                // Build the logical call stack by storing the current method being called
                // in our custom context stack.  Note that only calls made through tracked
                // interfaces end up on the stack, so we may miss some details (such as calls
                // within the implementing class).
                var stack = ContextStack.Current;
                Frame frame = new Frame(
                    invocation.Request.Target.GetType(),
                    invocation.Request.Method.Name,
                    invocation.Request.Method.GetParameters().Select(param => new Parameter(param.Name, param.ParameterType)).ToArray());
                var dispose = stack.Push(frame);
    
                // Make sure that the OperationContext flows across to deeper calls,
                // since we need it for ContextStack.  (And also it's cool to have it.)
                synchronizationContext = new OperationContextSynchronizationContext(frame);
    
                // Process the method being called.
                try
                {
                    invocation.Proceed();
                }
                catch (Exception ex)
                {
                    frame.SetException(ex);
                    throw;
                }
    
                var returnType = invocation.Request.Method.ReturnType;
                if (returnType == typeof(Task)
                    || (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)))
                {
                    Task task = invocation.ReturnValue as Task; // Could be a Task or a Task<>, and we honestly don't really care which.
                    frame.SetTask(task);
                    task.ContinueWith(t =>
                    {
                        // If we've succeeded, then remove.
                        if (!t.IsFaulted)
                        {
                            dispose.Dispose();
                        }
                        else
                        {
                            frame.SetException(t.Exception);
                        }
                    });
                }
                else
                {
                    // If we're not returning a Task, that means that we've fully processed the method.
                    // This will be hit for async void methods as well (which are, as far as we're
                    // concerned, fully processed).
                    dispose.Dispose();
                }
            }
            finally
            {
                //SynchronizationContext.SetSynchronizationContext(original);
                if (synchronizationContext != null)
                {
                    synchronizationContext.Dispose();
                }
            }
        }
    
    }
    

    IInterceptor 这里是Ninject.Extensions.Interception.IInterceptor

    为了使OperationContext 可用于每次通话,您需要使用此OperationContextSynchronizationContext

    public class OperationContextSynchronizationContext : SynchronizationContext, IDisposable
    {
    
        // Track the operation context to make sure that it flows through to the next call context.
    
        private readonly Frame _currentFrame;
        private readonly OperationContext _context;
        private readonly SynchronizationContext _previous;
    
        public OperationContextSynchronizationContext(Frame currentFrame)
        {
            _currentFrame = currentFrame;
            _context = OperationContext.Current;
            _previous = SynchronizationContext.Current;
            SynchronizationContext.SetSynchronizationContext(this);
        }
    
        public override void Post(SendOrPostCallback d, object state)
        {
            var context = _previous ?? new SynchronizationContext();
            context.Post(
                s =>
                {
                    OperationContext.Current = _context;
                    try
                    {
                        d(s);
                    }
                    catch (Exception ex)
                    {
                        // If we didn't have this, async void would be bad news bears.
                        // Since async void is "fire and forget," they happen separate
                        // from the main call stack.  We're logging this separately so
                        // that they don't affect the main call (and it just makes sense).
    
                         // implement your logging here
                    }
                },
                state
            );
        }
    
        private bool _disposed = false;
        public void Dispose()
        {
            if (!_disposed)
            {
                // Return to the previous context.
                SynchronizationContext.SetSynchronizationContext(_previous);
                _disposed = true;
            }
        }
    }
    

    然后您只需将其全部连接到您的 Ninject 绑定中:

    Bind<IBusinessLayer>().To<BusinessLayer>()
        .Intercept().With<SimpleContextGenerator>(); // Track all logical calls.
    

    请注意,这只能应用于接口到具体类的绑定,这就是我们不能以这种方式将服务本身放入堆栈的原因。我们可以包装每一个服务方法(比包装每一个调用更好),但我认为我们甚至不能用一个模块来做到这一点,因为服务框架不会有堆栈遍历的异常(如下)。

    最后,在IErrorHandler

    var context = ContextStack.Current.Stack;
    if (context.Any())
    {
        // Get all tasks that haven't yet completed and run them.  This will clear out any stack entries
        // that don't error.  This will run at most once; there should not be any situation where it
        // would run more than once.  As such, not doing a loop, though, if we find a situation where it
        // SHOULD run more than once, we should put the loop back in (but with a check for max loops).
        var frames = context.Where(frame => frame.Task != null && !frame.Task.IsCompleted);
        //while (tasks.Any())
        //{
            foreach (var frame in frames.ToList()) // Evaluate to prevent the collection from being modified while we're running the foreach.
            {
                // Make sure that each task has completed.  This may not be super efficient, but it
                // does allow each method to complete before we log, meaning that we'll have a good
                // indication of where all the errors are, and that seems worth it to me.
                // However, from what I've seen of the state of items that get here, it doesn't look
                // like anything here should error.
                try
                {
                    frame.Task.Wait();
                }
                catch (Exception taskEx)
                {
                    frame.SetException(taskEx);
                }
            }
        //}
    }
    
    // Prepare error information for one or more errors.
    // Always use the frames instead of the one that got us here,
    // since we have better information in the frames.
    
    var errorFrames = context.Where(frame => frame.Exception != null);
    if (errorFrames.Any())
    {
        // Unpack all exceptions so we have access to every actual exception in each frame.
        var unpackedErrorFrames = errorFrames.GroupBy(frame => frame.Exception.Unpack())
                                             .Select(group => new { Frame = group.First(), Exceptions = group.Key });
    
        // Expand out the exceptions.
        var expandedFrames = (from frame in unpackedErrorFrames
                              from exception in frame.Exceptions
                              select new { Frame = frame.Frame, Exception = exception });
    
        // Walk the stack.
        // The stack does not currently have the service itself in it, because I don't have an easy way to
        // wrap the service call to track the service frame and exception..
        var errorStacks = expandedFrames.GroupBy(frame => frame.Exception)
                                        .Select(group => new { Exception = group.Key, Stack = group.ToList() });
    
        // Log all exceptions.
        foreach (var stack in errorStacks)
        {
            var exception = stack.Exception;
            var @class = stack.Stack.First().Type;
            var method = stack.Stack.First().Method;
            var exceptionStack = stack.Stack.SelecT(s => s.Frame);
            // log exception here.
        }
    }
    else
    {
        // Well, if we don't have any error frames, but we still got here with an exception,
        // at least log that exception so that we know.
        // Since the service itself isn't in the stack, we'll get here if there are any
        // exceptions before we call the business layer.
    
        // log error here
    }
    

    这是Unpack 扩展方法:

    public static IEnumerable<Exception> Unpack(this Exception exception)
    {
        List<Exception> exceptions = new List<Exception>();
        var agg = exception as AggregateException;
        if (agg != null)
        {
            // Never add an AggregateException.
            foreach (var ex in agg.InnerExceptions)
            {
                exceptions.AddRange(ex.Unpack());
            }
        }
        else
        {
            exceptions.Add(exception);
        }
        return exceptions;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-01-23
      • 2017-09-14
      • 2014-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      相关资源
      最近更新 更多