возврат ожидания Method.Invoke()

Я большой поклонник СУХОГО кодирования, и мне нравится избегать шаблонного кода, насколько это возможно. Поэтому я реорганизовал весь мой канал WCF в класс AOP, который имеет дело с жизненным циклом канала WCF.

Я также большой поклонник асинхронного ожидания, особенно с WCF, так как теоретически это освобождает поток, который обычно находится в режиме ожидания ответа.

Поэтому я создал перехватчик в 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;
        }
    }

Однако, немного подумав над решением, я заметил, что в случае контракта WCF вида

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

GetInt может привести к неожиданным результатам. Во-первых, catch FaultException ничего не сделает. Во-вторых, я бы закрыл канал до того, как запрос вернется. Теоретически я мог бы переключиться на другой путь кода, если возвращаемый тип — Task. Но я не могу понять, как дождаться результатов Task‹>, а затем вернуть ожидаемое.

Это, конечно, особенно сложно, так как с АОП во время выполнения у меня не было бы доступа к использованию дженериков возвращаемого типа (без всей мешанины размышлений).

Любые идеи, как реализовать эту функцию как ожидаемую, которая закрывает канал при завершении и перехватывает/упорядочивает исключения в вызывающем потоке?


person Aron    schedule 04.03.2013    source источник


Ответы (1)


Чтобы сделать async инъекцию, вам придется заменить возвращенную задачу. Для удобства чтения кода я рекомендую заменить его методом async вместо использования ContinueWith.

Я не знаком с fluentAOP, но я сделал async инъекцию с помощью Castle DynamicProxy.

Если вы хотите использовать отражение, вам нужно сначала определить, является ли это вызовом async (т. е. является ли возвращаемый тип подклассом или равен typeof(Task). Если это вызов async, вам нужно будет используйте отражение, чтобы вытащить T из Task<T> и применить его к вашему собственному методу async:

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;
    }
}

Альтернативой является использование 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;
}
person Stephen Cleary    schedule 04.03.2013
comment
Проклятие. Как я думал. Придется использовать общий метод get. - person Aron; 04.03.2013
comment
Я играю с функциями dynamic, чтобы посмотреть, смогу ли я упростить это, но пока это не выглядит многообещающе. - person Stephen Cleary; 04.03.2013
comment
Не большой поклонник динамики, хотя бы потому, что вам нужно ссылаться на библиотеки CSharp. В любом случае, похоже, что ваш код лучший из возможных... Хотелось бы, чтобы C# был немного лучше для метапрограммирования. - person Aron; 05.03.2013
comment
Да, я не мог сделать это проще. И я полностью согласен с метапрограммированием! - person Stephen Cleary; 05.03.2013