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这样的替代解决方案?
简短的回答:远程调用不能在私有字段上工作。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开始
- 由于
Class2
是ContextBoundObject
,并且由于OhMyAttribute
认为当前上下文不可接受,因此在Context1中创建了Class2
的实例(我将其称为c2_real
),并且返回并存储在c2
中的是c2_real
的远程代理。 - 当
c2.Method1()
被调用时,它在远程代理上被调用。由于我们在Context0中,远程代理意识到它不在正确的上下文中,因此它切换到Context1,并执行Method1
中的代码。3.在Method1
中,我们调用NestedClass
构造函数,它使用c2.one
。在这种情况下,我们已经在Context1中,所以c2.one
不需要上下文切换,所以我们直接使用c2_real
对象。
现在,有问题的情况:
- 我们创建一个新的
NestedClass
在远程代理c2
中传递。这里没有发生上下文切换,因为NestedClass
不是ContextBoundObject
。 在
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
变量被提升并存储在嵌套类中。那么,这里发生的是
- 在对
c1.Method1()
的初始调用中,远程代理通知我们在Context0中,并且它需要切换到Context1。 - 最终调用
MoveNext
,再调用c1.one
。因为我们已经在Context1,没有上下文切换是必要的(所以问题不会发生)。 - 之后,由于注册了延续,将再次调用
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);
}
}
}