locked
Awaitable reflections RRS feed

  • Question

  • My API has a method that accepts a lambda. At run time I need my code to determine if a consumer passed me an awaitable lambda or a non-awaitable one. I assume this can be done through reflections. Can someone please provide me with an example of how this would be done.

    Thursday, November 29, 2012 6:54 PM

Answers

  • If you pass an Async Sub like that to your Foo method, the delegate that gets created by VB will be void-returning; this means that it won't be awaitable.  If instead you pass an Async Function to your Foo method, the delegate that gets created by VB will be a task-returning, and it will be awaitable.

    Since async lambdas can only be void, Task, or Task(Of T) returning, I would suggest only dealing with those three cases, and not worrying about other potentially-awaitable return values, since they will be rare (and non-existent if users are only use Async Sub or Async Function as the argument).  To handle that case, you can just call DynamicInvoke on the delegate to run it, and then look at the type of the object it hands back... if it's a Task, await it, if not, don't.

    That said, why not have multiple overloads of Foo, one that takes an Action and one that takes a Func(Of Task).  That way it's explicit what's happening, and you can await the result of the latter and not the former.

    For more on potential pitfalls with Async Sub, see http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx .

    Thursday, November 29, 2012 11:13 PM
    Moderator

All replies

  • Hi Perry-

    What do you mean by an awaitable lambda or a non-awaitable one?  You mean you want to check whether the type of object returned by the method implements a GetAwaiter method that returns something that meets the contract for being an awaiter?

    What does your method signature look like, and why do you need this capability?

    Thursday, November 29, 2012 7:58 PM
    Moderator
  • I think that most of the time, the correct solution to this is to have two overloads of your method: one that accepts synchronous lambda and one that accepts a lambda that returns Task (or Task<T>). This way, it's easy to recognize whether you have synchronous or asynchronous lambda and act accordingly. For example, the method Task.Run() does this.

    If you need to accept any awaitable, not just Task, you can use a short async lambda that basically transforms any awaitable into a Task:

    Task.Run(async () => await someAwaitable())
    Thursday, November 29, 2012 9:23 PM
  • Stephen, this is a scripting engine. I have a method with this signature:

    Public Sub Foo(lambda as [Delegate])

    The user passes a lambda to Foo(). They can pass either:

    Foo(Sub(ea as ScriptEventArgs)
        ...
        ...
        ea.Cancel = True
    End Sub)

    or

    Foo(Async Sub (ea as ScriptEventArgs)
        ....
        Dim result as Boolean = Await Somthing()
        ea.Cancel = result 
    End Sub)

    They may or may not need to use an Await in their lambda.

    The user has an option in their lambda to call: "ea.Cancel = True" which notifies the scripting engine to do some actions based on it. But on the scripting side I need to know if I should Await not not Await the lambda before I read the ea.Cancel from the ScriptEventArgs.

    This is how the scripting engine checks for ea.Cancel if the lambda is not awaitable: 

    Dim ea as new ScriptEventArgs()
    lambda.Invoke(ea)
    If ea.Cancel Then 
        ...
    End If
     

    but this would not work if the lambda awaits because the ea.Cancel is not set yet. So I need to do this.

    Dim ea as new ScriptEventArgs()
    Await lambda.Invoke(ea)
    If ea.Cancel Then 
        ...
    End If

    So I need a way to determine through reflections if the lambda passed in is marked as Async. and take the appropriate invocation action on the engine side. 


    Thursday, November 29, 2012 10:11 PM
  • If you pass an Async Sub like that to your Foo method, the delegate that gets created by VB will be void-returning; this means that it won't be awaitable.  If instead you pass an Async Function to your Foo method, the delegate that gets created by VB will be a task-returning, and it will be awaitable.

    Since async lambdas can only be void, Task, or Task(Of T) returning, I would suggest only dealing with those three cases, and not worrying about other potentially-awaitable return values, since they will be rare (and non-existent if users are only use Async Sub or Async Function as the argument).  To handle that case, you can just call DynamicInvoke on the delegate to run it, and then look at the type of the object it hands back... if it's a Task, await it, if not, don't.

    That said, why not have multiple overloads of Foo, one that takes an Action and one that takes a Func(Of Task).  That way it's explicit what's happening, and you can await the result of the latter and not the former.

    For more on potential pitfalls with Async Sub, see http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx .

    Thursday, November 29, 2012 11:13 PM
    Moderator
  • Good stuff. The pitfalls is exactly what I have had to deal with.
    • Edited by Perry Manole Wednesday, December 5, 2012 1:13 PM
    Wednesday, December 5, 2012 1:10 PM