Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: AronОтветов: 1

Я большой поклонник СУХОГО кодирования, и мне нравится избегать шаблонного кода, насколько это возможно. Поэтому я реорганизовал весь мой канал 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‹>, а затем вернуть ожидаемое.

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

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

async-awaitaopmethodinfo

Ответы

Ответ 1 · рейтинг 7 · Stephen Cleary

Чтобы сделать 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;
}

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы