ContextBoundObject在Await之后抛出远程处理错误

本文关键字:处理 错误 程处理 Await 之后 ContextBoundObject | 更新日期: 2023-09-27 17:50:05

我有一些日志代码,用于拦截使用ContextBoundObject s和ContextAttribute的方法调用。该代码基于code Project示例。

这一切都很好,直到我们开始将这个库与利用async和await的代码一起使用。现在我们在运行代码时会得到远程错误。下面是再现这个问题的一个简单示例:

public class OhMyAttribute : ContextAttribute
{
    public OhMyAttribute() : base("OhMy")
    {
    }
}
[OhMy]
public class Class1 : ContextBoundObject
{
    private string one = "1";
    public async Task Method1()
    {
        Console.WriteLine(one);
        await Task.Delay(50);
        Console.WriteLine(one);
    }
}

当我们调用Method1时,我们在第二个Console.WriteLine上得到以下RemotingException:

Remoting cannot find field 'one' on type 'WindowsFormsApplication1.Class1'.

是否有办法使用c#内置的方法来解决这个问题,或者我们必须寻找像PostSharp这样的替代解决方案?

ContextBoundObject在Await之后抛出远程处理错误

简短的回答:远程调用不能在私有字段上工作。async/await重写导致尝试对私有字段进行远程调用。

问题可以在没有async/await的情况下复制。这样演示有助于理解async/await的情况:

[OhMy]
public class Class2 : ContextBoundObject
{
    private string one = "1";
    public void Method1()
    {
        var nc = new NestedClass(this);
    }
    public class NestedClass
    {
        public NestedClass(Class2 c2)
        {
            Console.WriteLine(c2.one);  // Note: nested classes are allowed access to outer classes privates
        }
    }
}
static void Main(string[] args)
{
    var c2 = new Class2();
    // This call causes no problems:
    c2.Method1();
    // This, however, causes the issue.
    var nc = new Class2.NestedClass(c2);
}

让我们一行一行地看一下发生了什么:

    在Main中,我们从Context0开始
  1. 由于Class2ContextBoundObject,并且由于OhMyAttribute认为当前上下文不可接受,因此在Context1中创建了Class2的实例(我将其称为c2_real),并且返回并存储在c2中的是c2_real的远程代理。
  2. c2.Method1()被调用时,它在远程代理上被调用。由于我们在Context0中,远程代理意识到它不在正确的上下文中,因此它切换到Context1,并执行Method1中的代码。3.在Method1中,我们调用NestedClass构造函数,它使用c2.one。在这种情况下,我们已经在Context1中,所以c2.one不需要上下文切换,所以我们直接使用c2_real对象。

现在,有问题的情况:

  1. 我们创建一个新的NestedClass在远程代理c2中传递。这里没有发生上下文切换,因为NestedClass不是ContextBoundObject
  2. NestedClass函数中,它访问c2.one。远程代理注意到我们仍在Context0中,因此它尝试将此调用远程到Context1。这个失败是因为c2.one是私有字段。您将看到在Object.GetFieldInfo中,它只查找公共字段:

    private FieldInfo GetFieldInfo(String typeName, String fieldName)
    {
        // ...
        FieldInfo fldInfo = t.GetField(fieldName, BindingFlags.Public | 
                                                    BindingFlags.Instance | 
                                                    BindingFlags.IgnoreCase);
        if(null == fldInfo)
        {
    #if FEATURE_REMOTING 
            throw new RemotingException(String.Format(
                CultureInfo.CurrentCulture, Environment.GetResourceString("Remoting_BadField"),
                                                fieldName, typeName));            
        // ...
        }
        return fldInfo;
    }
    

那么, async/await如何最终导致同样的问题?

async/await导致你的Class1被重写,这样它就使用了一个带状态机的嵌套类(使用ILSpy生成):

public class Class1 : ContextBoundObject
{
    // ...
    private struct <Method1>d__0 : IAsyncStateMachine
    {
        public int <>1__state;
        public AsyncTaskMethodBuilder <>t__builder;
        public Class1 <>4__this;
        private TaskAwaiter <>u__$awaiter1;
        private object <>t__stack;
        void IAsyncStateMachine.MoveNext()
        {
            try
            {
                int num = this.<>1__state;
                if (num != -3)
                {
                    TaskAwaiter taskAwaiter;
                    if (num != 0)
                    {
                        Console.WriteLine(this.<>4__this.one);
                        taskAwaiter = Task.Delay(50).GetAwaiter();
                        if (!taskAwaiter.IsCompleted)
                        {
                            this.<>1__state = 0;
                            this.<>u__$awaiter1 = taskAwaiter;
                            this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Class1.<Method1>d__0>(ref taskAwaiter, ref this);
                            return;
                        }
                    }
                    else
                    {
                        taskAwaiter = this.<>u__$awaiter1;
                        this.<>u__$awaiter1 = default(TaskAwaiter);
                        this.<>1__state = -1;
                    }
                    taskAwaiter.GetResult();
                    taskAwaiter = default(TaskAwaiter);
                    Console.WriteLine(this.<>4__this.one);
                }
            }
            catch (Exception exception)
            {
                this.<>1__state = -2;
                this.<>t__builder.SetException(exception);
                return;
            }
            this.<>1__state = -2;
            this.<>t__builder.SetResult();
        }
        // ... 
    }
    private string one = "1";
    public Task Method1()
    {
        Class1.<Method1>d__0 <Method1>d__;
        <Method1>d__.<>4__this = this;
        <Method1>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
        <Method1>d__.<>1__state = -1;
        AsyncTaskMethodBuilder <>t__builder = <Method1>d__.<>t__builder;
        <>t__builder.Start<Class1.<Method1>d__0>(ref <Method1>d__);
        return <Method1>d__.<>t__builder.Task;
    }
}

重要的是要注意

  • 它创建了一个嵌套结构,可以访问Class1的私有值
  • this变量被提升并存储在嵌套类中。

那么,这里发生的是

  1. 在对c1.Method1()的初始调用中,远程代理通知我们在Context0中,并且它需要切换到Context1。
  2. 最终调用MoveNext,再调用c1.one。因为我们已经在Context1,没有上下文切换是必要的(所以问题不会发生)。
  3. 之后,由于注册了延续,将再次调用MoveNext来执行await之后的其余代码。然而,对MoveNext的调用不会发生在对Class1的一个方法的调用中。因此,这次执行代码c1.one时,我们将处于Context0。远程代理注意到我们在Context0中,并尝试进行上下文切换。由于c1.one是私有字段,这将导致与上面相同的失败。

处理:我不确定一般的解决方案,但对于这种特定情况,您可以通过不使用方法中的this引用来解决问题。例如:

public async Task Method1()
{
    var temp = one;
    Console.WriteLine(temp);
    await Task.Delay(50);
    Console.WriteLine(temp);
}

这是一个更通用的解决方法。

存在以下不足:

  • 不支持在ContextBoundObject中修改SynchronizationContext。在这种情况下,它将throw
  • 不支持当SynchronizationContext.Current为空时使用await TaskScheduler.Current不是TaskScheduler.Default的情况。在此场景中,通常await将捕获TaskScheduler并使用它来发布剩余的工作,但由于此解决方案设置了SynchronizationContext,因此不会捕获TaskScheduler。因此,当检测到这种情况时,它将throw .
  • 它不支持使用.ConfigureAwait(false),因为这将导致SynchronizationContext不被捕获。不幸的是,我没能发现这种情况。但是,如果用户确实想获得类似.ConfigureAwait(false)的底层传递SynchronizationContext的行为,他们可以使用自定义的awaiter(参见https://stackoverflow.com/a/22417031/495262)。

这里有一件有趣的事情,我试图创建一个"通过"SynchronizationContext。也就是说,我不想覆盖任何现有的SynchronizationContext,而是保留其行为,并在其上分层在适当的上下文中进行工作的行为。欢迎对更好的方法提出任何意见。

    using System;
    using System.Runtime.Remoting.Activation;
    using System.Runtime.Remoting.Contexts;
    using System.Runtime.Remoting.Messaging;
    using System.Threading;
    using System.Threading.Tasks;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var c1 = new Class1();
                var t = c1.Method1();
                Func<Task> f = c1.Method1;
                f.BeginInvoke(null, null);
                Console.ReadKey();
            }
        }
        [MyContext]
        public class Class1 : ContextBoundObject
        {
            private string one = "1";
            public async Task Method1()
            {
                Console.WriteLine(one);
                await Task.Delay(50);
                Console.WriteLine(one);
            }
        }
        sealed class MyContextAttribute : ContextAttribute
        {
            public MyContextAttribute()
                : base("My")
            {
            }
            public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
            {
                if (ctorMsg == null)
                    throw new ArgumentNullException("ctorMsg");
                ctorMsg.ContextProperties.Add(new ContributeInstallContextSynchronizationContextMessageSink());
            }
            public override bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)
            {
                return false;
            }
        }
        sealed class ContributeInstallContextSynchronizationContextMessageSink : IContextProperty, IContributeServerContextSink
        {
            public ContributeInstallContextSynchronizationContextMessageSink()
            {
            }
            public IMessageSink GetServerContextSink(IMessageSink nextSink)
            {
                return new InstallContextSynchronizationContextMessageSink(nextSink);
            }
            public string Name { get { return "ContributeInstallContextSynchronizationContextMessageSink"; } }
            public bool IsNewContextOK(Context ctx)
            {
                return true;
            }
            public void Freeze(Context ctx)
            {
            }
        }
        sealed class InstallContextSynchronizationContextMessageSink : IMessageSink
        {
            readonly IMessageSink m_NextSink;
            public InstallContextSynchronizationContextMessageSink(IMessageSink nextSink)
            {
                m_NextSink = nextSink;
            }
            public IMessageSink NextSink
            {
                get { return m_NextSink; }
            }
            public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
            {
                var contextSyncContext = new ContextSynchronizationContext(SynchronizationContext.Current);
                var syncContextReplacer = new SynchronizationContextReplacer(contextSyncContext);
                DelegateMessageSink.SyncProcessMessageDelegate replySyncDelegate = (n, m) => SyncProcessMessageDelegateForAsyncReply(n, m, syncContextReplacer);
                var newReplySink = new DelegateMessageSink(replySink, replySyncDelegate, null);
                return m_NextSink.AsyncProcessMessage(msg, newReplySink);
            }
            public IMessage SyncProcessMessage(IMessage msg)
            {
                var contextSyncContext = new ContextSynchronizationContext(SynchronizationContext.Current);
                using (new SynchronizationContextReplacer(contextSyncContext))
                {
                    var ret = m_NextSink.SyncProcessMessage(msg);
                    return ret;
                }
            }
            private IMessage SyncProcessMessageDelegateForAsyncReply(IMessageSink nextSink, IMessage msg, SynchronizationContextReplacer syncContextReplacer)
            {
                syncContextReplacer.Dispose();
                return nextSink.SyncProcessMessage(msg);
            }
            private void PreChecks()
            {
                if (SynchronizationContext.Current != null)
                    return;
                if (TaskScheduler.Current != TaskScheduler.Default)
                    throw new InvalidOperationException("InstallContextSynchronizationContextMessageSink does not support calling methods with SynchronizationContext.Current as null while Taskscheduler.Current is not TaskScheduler.Default");
            }
        }
        sealed class SynchronizationContextReplacer : IDisposable
        {
            SynchronizationContext m_original;
            SynchronizationContext m_new;
            public SynchronizationContextReplacer(SynchronizationContext syncContext)
            {
                m_original = SynchronizationContext.Current;
                m_new = syncContext;
                SynchronizationContext.SetSynchronizationContext(m_new);
            }
            public void Dispose()
            {
                // We don't expect the SynchronizationContext to be changed during the lifetime of the SynchronizationContextReplacer
                if (SynchronizationContext.Current != m_new)
                    throw new InvalidOperationException("SynchronizationContext was changed unexpectedly.");
                SynchronizationContext.SetSynchronizationContext(m_original);
            }
        }
        sealed class ContextSynchronizationContext : PassThroughSynchronizationConext
        {
            readonly Context m_context;
            private ContextSynchronizationContext(SynchronizationContext passThroughSyncContext, Context ctx)
                : base(passThroughSyncContext)
            {
                if (ctx == null)
                    throw new ArgumentNullException("ctx");
                m_context = ctx;
            }
            public ContextSynchronizationContext(SynchronizationContext passThroughSyncContext)
                : this(passThroughSyncContext, Thread.CurrentContext)
            {
            }
            protected override SynchronizationContext CreateCopy(SynchronizationContext copiedPassThroughSyncContext)
            {
                return new ContextSynchronizationContext(copiedPassThroughSyncContext, m_context);
            }
            protected override void CreateSendOrPostCallback(SendOrPostCallback d, object state)
            {
                CrossContextDelegate ccd = () => d(state);
                m_context.DoCallBack(ccd);
            }
        }
        abstract class PassThroughSynchronizationConext : SynchronizationContext
        {
            readonly SynchronizationContext m_passThroughSyncContext;
            protected PassThroughSynchronizationConext(SynchronizationContext passThroughSyncContext)
                : base()
            {
                m_passThroughSyncContext = passThroughSyncContext;
            }
            protected abstract void CreateSendOrPostCallback(SendOrPostCallback d, object state);
            protected abstract SynchronizationContext CreateCopy(SynchronizationContext copiedPassThroughSyncContext);
            public sealed override void Post(SendOrPostCallback d, object state)
            {
                var d2 = CreateSendOrPostCallback(d);
                if (m_passThroughSyncContext != null)
                    m_passThroughSyncContext.Post(d2, state);
                else
                    base.Post(d2, state);
            }
            public sealed override void Send(SendOrPostCallback d, object state)
            {
                var d2 = CreateSendOrPostCallback(d);
                if (m_passThroughSyncContext != null)
                    m_passThroughSyncContext.Send(d2, state);
                else
                    base.Send(d2, state);
            }
            public sealed override SynchronizationContext CreateCopy()
            {
                var copiedSyncCtx = m_passThroughSyncContext != null ? m_passThroughSyncContext.CreateCopy() : null;
                return CreateCopy(copiedSyncCtx);
            }
            public sealed override void OperationCompleted()
            {
                if (m_passThroughSyncContext != null)
                    m_passThroughSyncContext.OperationCompleted();
                else
                    base.OperationCompleted();
            }
            public sealed override void OperationStarted()
            {
                if (m_passThroughSyncContext != null)
                    m_passThroughSyncContext.OperationStarted();
                else
                    base.OperationStarted();
            }
            public sealed override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
            {
                return m_passThroughSyncContext != null ?
                    m_passThroughSyncContext.Wait(waitHandles, waitAll, millisecondsTimeout) :
                    base.Wait(waitHandles, waitAll, millisecondsTimeout);
            }
            private SendOrPostCallback CreateSendOrPostCallback(SendOrPostCallback d)
            {
                SendOrPostCallback sopc = s => CreateSendOrPostCallback(d, s);
                return sopc;
            }
        }
        sealed class DelegateMessageSink : IMessageSink
        {
            public delegate IMessage SyncProcessMessageDelegate(IMessageSink nextSink, IMessage msg);
            public delegate IMessageCtrl AsyncProcessMessageDelegate(IMessageSink nextSink, IMessage msg, IMessageSink replySink);
            readonly IMessageSink m_NextSink;
            readonly SyncProcessMessageDelegate m_syncProcessMessageDelegate;
            readonly AsyncProcessMessageDelegate m_asyncProcessMessageDelegate;
            public DelegateMessageSink(IMessageSink nextSink, SyncProcessMessageDelegate syncProcessMessageDelegate, AsyncProcessMessageDelegate asyncProcessMessageDelegate)
            {
                m_NextSink = nextSink;
                m_syncProcessMessageDelegate = syncProcessMessageDelegate;
                m_asyncProcessMessageDelegate = asyncProcessMessageDelegate;
            }
            public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
            {
                return (m_asyncProcessMessageDelegate != null) ?
                    m_asyncProcessMessageDelegate(m_NextSink, msg, replySink) :
                    m_NextSink.AsyncProcessMessage(msg, replySink);
            }
            public IMessageSink NextSink
            {
                get { return m_NextSink; }
            }
            public IMessage SyncProcessMessage(IMessage msg)
            {
                return (m_syncProcessMessageDelegate != null) ?
                    m_syncProcessMessageDelegate(m_NextSink, msg) :
                    m_NextSink.SyncProcessMessage(msg);
            }
        }
    }