在呼叫时

时间:2017-05-24 03:38:35

标签: c# postsharp

当我们调用异步方法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.OnEntryMethodInterceptionAspect.Invoke将知道此调用是等待直接或稍后。

2 个答案:

答案 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设计基于您改变调用方式的想法。 (例如,始终将结果缓存)。

如果差异在于你如何调用它,为什么不将它包装在一个函数中。