Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

return await Method.Invoke()

I am a big fan of DRY coding, and I like to avoid boiler plate code as much as possible. Hence I have refactored all of my WCF channel faff into an AOP class, which deals with the lifecycle of the WCF channel.

I also am a big fan of async-await, especially with WCF, as it would in theory free up a thread that would normally be sleep-waiting for the response.

So I created an interceptor in the fluentAOP lib

    private static object InvokeOnChannel(IMethodInvocation methodInvocation)
    {
        var proxy = _factory.CreateChannel();
        var channel = (IChannel) proxy;
        try
        {
            channel.Open();
            var ret = methodInvocation.Method.Invoke(proxy, methodInvocation.Arguments);
            channel.Close();
            return ret;
        }
        catch (FaultException ex)
        {
            if (ex.InnerException != null)
                throw ex.InnerException;
            throw;
        }
        catch(Exception)
        {
            channel.Abort();
            throw;
        }
    }

However, when thinking a little about the solution I noted that in the case of a WCF contract of the form

[ServiceContract]
public interface IFoo
{
    [OperationContract]
    Task<int> GetInt();
}

GetInt would have unexpected results. Firstly the catch FaultException would do nothing. Secondly I would be closing the channel before the request returns. I could in theory switch to another code path if the return type is of Task. But I can't figure out how to await the results of a Task<> and then return an awaitable.

This of course is especially difficult since with runtime AOP I would not have access be able to use generics of the return type (without the whole bodge of reflection).

Any ideas how to implement this function as an awaitable, which closes the channel on complete and catches/marshals exceptions to the calling thread?

like image 501
Aron Avatar asked Mar 04 '13 10:03

Aron


People also ask

What is the return type of await?

The behavior of async / await is similar to combining generators and promises. Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.

How do I return from async method?

If you use a Task return type for an async method, a calling method can use an await operator to suspend the caller's completion until the called async method has finished. In the following example, the WaitAndApologizeAsync method doesn't contain a return statement, so the method returns a Task object.

What is the return type of async await in C#?

If the task does not have a result, then the return type of the async method can either be Task or void . Returning a Task is usually favourable to returning void , since a Task is awaitable while void is not. However, there are situations where void is the preferred return type.

What does the await keyword do C#?

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.


1 Answers

To do async injection, you'll have to replace your returned task. For code readability, I recommend replacing it with an async method instead of using ContinueWith.

I'm not familiar with fluentAOP, but I've done async injection with Castle DynamicProxy.

If you want to use reflection, what you'll want to do is first determine if it's an async call (i.e., if the return type is a subclass of or is equal to typeof(Task). If it's an async call, then you will need to use reflection to pull the T out of Task<T> and apply it to your own async method:

private static MethodInfo handleAsync = ...; // point this to HandleAsync<T>

// Only called if the return type is Task/Task<T>
private static object InvokeAsyncOnChannel(IMethodInvocation methodInvocation)
{
    var proxy = _factory.CreateChannel();
    var channel = (IChannel) proxy;
    try
    {
        channel.Open();
        var task = methodInvocation.Method.Invoke(proxy, methodInvocation.Arguments) as Task;
        object ret;
        if (task.GetType() == typeof(Task))
            ret = HandleAsync(task, channel);
        else
            ret = handleAsync.MakeGenericMethod(task.GetType().GetGenericParameters()).Invoke(this, task, channel);
        return ret;
    }
    catch (FaultException ex)
    {
        if (ex.InnerException != null)
            throw ex.InnerException;
        throw;
    }
    catch(Exception)
    {
        channel.Abort();
        throw;
    }
}

private static async Task HandleAsync(Task task, IChannel channel)
{
    try
    {
        await task;
        channel.Close();
    }
    catch (FaultException ex)
    {
        if (ex.InnerException != null)
            throw ex.InnerException;
        throw;
    }
    catch(Exception)
    {
        channel.Abort();
        throw;
    }
}

private static async Task<T> HandleAsync<T>(Task task, IChannel channel)
{
    try
    {
        var ret = await (Task<T>)task;
        channel.Close();
        return ret;
    }
    catch (FaultException ex)
    {
        if (ex.InnerException != null)
            throw ex.InnerException;
        throw;
    }
    catch(Exception)
    {
        channel.Abort();
        throw;
    }
}

An alternative is to use dynamic:

private static object InvokeOnChannel(IMethodInvocation methodInvocation)
{
    var proxy = _factory.CreateChannel();
    var channel = (IChannel) proxy;
    try
    {
        channel.Open();
        dynamic result = methodInvocation.Method.Invoke(proxy, methodInvocation.Arguments);
        return Handle(result, channel);
    }
    catch (FaultException ex)
    {
        if (ex.InnerException != null)
            throw ex.InnerException;
        throw;
    }
    catch(Exception)
    {
        channel.Abort();
        throw;
    }
}

private static async Task Handle(Task task, IChannel channel)
{
    try
    {
        await task;
        channel.Close();
    }
    catch (FaultException ex)
    {
        if (ex.InnerException != null)
            throw ex.InnerException;
        throw;
    }
    catch(Exception)
    {
        channel.Abort();
        throw;
    }
}

private static async Task<T> Handle<T>(Task<T> task, IChannel channel)
{
    await Handle((Task)task, channel);
    return await task;
}

private static T Handle<T>(T result, IChannel channel)
{
    channel.Close();
    return result;
}
like image 79
Stephen Cleary Avatar answered Oct 05 '22 07:10

Stephen Cleary