当我们调用异步方法A时,我们可以直接等待(代码将按顺序执行),或稍后等待(内部等待之后的某些代码将在新任务中异步执行)。
现在,我在方法A中使用了postharp方面[OnMethodBoundaryAspect],我想在等待或直接等待时注入不同的代码,我怎么知道呢?
public class Program
{
[PostsharpAttr]
public async Task<int> B()
{
var value = await A();
// [operation B] this opeation will execute after operation A
return value;
}
[PostsharpAttr]
public async Task<int> C()
{
var valueT = A();
// [operation C] this operation may parallel execute with operation A
var value = await valueT;
return value;
}
[PostsharpAttr]
public async Task<int> A()
{
await Task.Delay(1000);
// [operation A]
return 1;
}
}
public class PostsharpAttrAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
bool awaitDirectly = false;
var asyncAttr = args.Method.GetCustomAttributes<AsyncStateMachineAttribute>();
if (asyncAttr != null)
{// is async method
if (awaitDirectly)
{
// if await directly
}
else
{
// if await later
}
}
}
}
修改
等待异步方法将生成两行IL代码,这两行代码将在一起直接等待方法。async Task<int> B()
{
var value = await A();
var d = D();
return value + d;
}
async Task<int> C()
{
var valueT = A();
var d = D();
var value = await valueT;
return value + d;
}
方法B IL代码的一部分:
L_0010: callvirt instance class [mscorlib]System.Threading.Tasks.Task`1<int32> PostsharpTest.Temp::A()
L_0015: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> [mscorlib]System.Threading.Tasks.Task`1<int32>::GetAwaiter()
方法C IL代码的一部分:
L_0010: callvirt instance class [mscorlib]System.Threading.Tasks.Task`1<int32> PostsharpTest.Temp::A()
L_0015: ldarg.0
L_0016: ldarg.0
L_0017: ldfld class PostsharpTest.Temp PostsharpTest.Temp/<C>d__2::<>4__this
L_001c: callvirt instance int32 PostsharpTest.Temp::D()
L_0021: stfld int32 PostsharpTest.Temp/<C>d__2::<d>5__1
L_0026: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> [mscorlib]System.Threading.Tasks.Task`1<int32>::GetAwaiter()
如果等待直接,task.GetAwaiter就在async方法调用之下。
所以我认为postharp可以找到asyns方法调用并确定它是否在编译时直接等待,然后在调用方法之前注入一些代码(设置postsharp属性属性)。
如果postharp可以执行此操作,则OnMethodBoundaryAspect.OnEntry
或MethodInterceptionAspect.Invoke
将知道此调用是等待直接或稍后。
答案 0 :(得分:0)
我希望在等待或直接等待时注入不同的代码,我怎么知道呢?
你无法知道这一点。你要求代码展望未来。
以这种方式思考:当调用async
方法时,它必须(同步)返回Task
。等待该任务时,无法通知该方法。
或者进入同步世界一分钟。考虑一个返回整数的方法。然后,您的问题将成为“我希望在Console
显示该整数时注入不同的代码,而不是在TextBox
”中显示该整数时。我们根本不可能知道调用代码将来会对您的返回值做什么。
现在,你可以返回一些完全不同的东西(不是与Aspect有关,而是与我相处)。你可以返回一个类似整数的对象,它在转换为整数时观察它的调用代码,如果它转到Console
则会有不同的结果。同样,您可以从async
方法返回一个自定义等待方法,该方法在实际等待时采取一些特殊操作。但是这些方法很简陋而且很奇怪。
最好退后一步,想一想你实际上要解决的问题,并找到更合理的解决方案。
答案 1 :(得分:0)
我认为重要的是要意识到等待结果的意义。
如果启动异步方法,则可以立即创建任务。这可能意味着它会立即执行。所以任何方法边界(入口)代码都已经执行过了。
那说你可以写这样的代码。
public class PostsharpAttrAttribute : MethodInterceptionAspect
{
bool awaitDirectly = false;
public override void OnInvoke(MethodInterceptionArgs args)
{
var asyncAttr = args.Method.GetCustomAttributes<AsyncStateMachineAttribute>();
if (asyncAttr != null)
{// is async method
if (awaitDirectly)
{
base.OnInvoke();
var task = (Task)args.ReturnValue;
task.Wait()
}
else
{
base.OnInvoke();
}
}
}
}
你可以通过在属性中将awaitDirectly设置为true来使你的代码同步(直接等待所有事情),无论它实际上是如何被调用的。
异步方法返回任务。该任务可能随时执行,但这样你就可以强制等待它。
注意:在c#中有一些死锁选项,如果你混合任务&amp;异步/ AWAIT。 ConfigureAwait(false)是解决这个问题的一种方法,但我不确定如何将其应用于postsharp。我希望这至少能给你一些如何实验的想法。继续
更新:即使IL有所不同,我也不确定这是否足够可靠,你可以做你想做的事情(优化可能会在不同的情况下导致不同的结果)
此外,postharp设计基于您改变调用方式的想法。 (例如,始终将结果缓存)。
如果差异在于你如何调用它,为什么不将它包装在一个函数中。