【问题标题】:ContextBoundObject Throws a Remoting Error After AwaitContextBoundObject 在等待后引发远程错误
【发布时间】:2014-03-13 23:14:47
【问题描述】:

我编写了一些日志记录代码,用于拦截使用 ContextBoundObject 和 ContextAttribute 的方法调用。该代码基于代码项目sample

这一切都很好,直到我们开始将此库与利用异步和等待的代码一起使用。现在我们在运行代码时遇到远程错误。这是一个重现问题的简单示例:

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 这样的替代解决方案?

【问题讨论】:

  • 我不知道您要做什么,但该代码甚至不在 Class1 类中。检查异常的堆栈跟踪。

标签: c# async-await custom-attributes


【解决方案1】:

简答:远程调用不适用于私有字段。 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);
}

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

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

现在,有问题的案例:

  1. 我们创建一个新的NestedClass,传入远程代理c2。这里没有上下文切换,因为NestedClass 不是ContextBoundObject
  2. NestedClass ctor 中,它访问 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);
}

或者切换到使用私有属性而不是字段。

【讨论】:

    【解决方案2】:

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

    它有以下不足:

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

    这里有一个有趣的事情是我试图创建一个“通过”SynchronizationContext。也就是说,我不想覆盖任何现有的SynchronizationContext,而是保留它的行为并在其上叠加在适当的上下文中进行工作的行为。欢迎任何关于更好方法的 cmets。

        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);
                }
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2022-07-25
      • 2019-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多