return await Method.Invoke()
Asked Answered
S

1

5

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?

Scirrhus answered 4/3, 2013 at 10:30 Comment(0)
L
7

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;
}
Lillalillard answered 4/3, 2013 at 13:56 Comment(4)
Damn. As I thought. Have to use generic get method.Scirrhus
I'm playing around with dynamic functions to see if I can simplify this further, but it doesn't look promising so far.Lillalillard
Not a big fan of dynamic, if only because you have to reference the CSharp libs. Anyways, looks like your code is the best possible... Do kind of wish C# was a bit better for metaprogramming.Scirrhus
Yeah, I couldn't get it simpler. And I totally agree re metaprogramming!Lillalillard

© 2022 - 2024 — McMap. All rights reserved.