Joh*_*ner 3 c# custom-attributes async-await
我有一些日志代码,使用ContextBoundObject和ContextAttribute编写拦截方法调用.该代码基于代码项目示例.
这一切都运行良好,直到我们开始使用此库与利用异步和等待的代码.现在我们在运行代码时遇到了远程错误.这是一个重现问题的简单示例:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
当我们调用时,Method1
我们RemotingException
在第二个上面得到以下内容Console.WriteLine
:
Remoting cannot find field 'one' on type 'WindowsFormsApplication1.Class1'.
Run Code Online (Sandbox Code Playgroud)
有没有办法使用内置的C#方法来解决这个问题,还是我们必须看看像PostSharp这样的替代解决方案?
简短回答:远程呼叫不适用于私人领域.该async
/ await
重写导致企图使在私人领域中的远程调用.
没有async
/ 可以复制这个问题await
.以这种方式展示它有助于理解async
/ await
case 中发生的事情:
[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);
}
Run Code Online (Sandbox Code Playgroud)
让我们逐步了解一下发生的事情:
Class2
是a ContextBoundObject
并且因为OhMyAttribute
认为当前上下文不可接受,所以Class2
在Context1中创建了一个实例(我将调用它c2_real
,并且返回并存储的c2
是远程代理)c2_real
.c2.Method1()
被调用时,它被称为在远程代理.由于我们在Context0中,远程代理意识到它不在正确的上下文中,所以它切换到Context1,并Method1
执行其中的代码.3.a在Method1
我们内部调用NestedClass
使用的构造函数c2.one
.在这种情况下,我们已经在Context1中,因此c2.one
不需要上下文切换,因此我们c2_real
直接使用该对象.现在,有问题的案例:
NestedClass
在远程代理中创建一个新的传递c2
.这里没有上下文切换,因为NestedClass
不是a ContextBoundObject
. 在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;
}
Run Code Online (Sandbox Code Playgroud)因此,如何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;
}
}
Run Code Online (Sandbox Code Playgroud)
需要注意的重要一点是
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);
}
Run Code Online (Sandbox Code Playgroud)
或者切换到使用私有属性而不是字段.
归档时间: |
|
查看次数: |
805 次 |
最近记录: |