仅在需要时将对象包装到任务<object>中

时间:2017-03-03 13:37:23

标签: c# async-await

我正在扩展现有的Rpc库以支持异步方法。

在当前的实现中,我使用工厂方法来构建类型化的委托,就像这样(我省略了一些与问题无关的实现细节):

    public static Func<ServiceType, PayloadType, object> BuildServiceMethodInvocation<ServiceType>(MethodInfo serviceMethod)
    {
        return (service, payload) =>
        {
            try
            {
                // payload is mapped onto an object[]
                var parameters = ReadPayload(payload)
                var result = serviceMethod.Invoke(service, parameters);

                return result;
            }
            catch (TargetInvocationException e)
            {
                // bla bla bla handle the error
                throw;
            }
        };
    }

然后将生成的object传递给序列化类,该类将处理每种情况。

现在我想支持异步方法,即返回Task<T>

的方法

我想更改BuildServiceMethodInvocation的签名,使其成为Func<ServiceType, PayloadType, Task<object>>

我不知道如何轻松地在Task<object>中包装每个可能的值:它应该处理普通值,还要处理Task<T>(装入对象)。

有什么想法吗?

编辑: serviceMethod可以返回一个字符串,我希望得到Task<object>返回字符串值。 但是serviceMethod可以返回Task<int>,在这种情况下,我想返回Task<object>返回int值(装入对象)。

2 个答案:

答案 0 :(得分:1)

使语法在语法上有效的最简单方法是Task.FromResult。现在,这并不会使您的方法异步。但是没有额外的装箱或拆箱,至少不会比以前更多。

public static Func<ServiceType, PayloadType, Task> BuildServiceMethodInvocation<ServiceType>(MethodInfo serviceMethod)
{
    return (service, payload) =>
    {
        try
        {
            // payload is mapped onto an object[]
            var parameters = ReadPayload(payload)
            var result = serviceMethod.Invoke(service, parameters);

            // forward the task if it already *is* a task
            var task = (result as Task) ?? Task.FromResult(result);

            return task;
        }
        catch (TargetInvocationException e)
        {
            // bla bla bla handle the error
            throw;
        }
    };
}

答案 1 :(得分:0)

好的,将@Evk(以及John Skeet和Marc Gravell)的提示放在一起,我想出了一个似乎有用的解决方案。

以这种方式更改了调用:

public static Func<ServiceType, PayloadType, object> BuildServiceMethodInvocation<ServiceType>(MethodInfo serviceMethod)
{
    var taskWrappingFunc = BuildTaskWrapperFunction(serviceMethod.ReturnType);

    return (service, payload) =>
    {
        try
        {
            // payload is mapped onto an object[]
            var parameters = ReadPayload(payload)
            var result = serviceMethod.Invoke(service, parameters);

            return taskWrappingFunc(result);
        }
        catch (TargetInvocationException e)
        {
            // bla bla bla handle the error
            throw;
        }
    };
}

BuildTaskWrapperFunction负责构建一个将接受一个对象的函数,并返回一个Task<object>,实际上根据需要提取和重新装箱结果。

    public static Func<object, Task<object>> BuildTaskWrapperFunction(Type returnType)
    {
        // manage Task<T> types
        Type taskResultType;
        if (IsATaskOfT(returnType, out taskResultType))
        {
            var f = MakeTaskWrappingMethodInfo.MakeGenericMethod(returnType);
            return (Func<object, Task<object>>)f.Invoke(null, new object[0]);
        } 

        // Manage Task
        if (typeof(Task).IsAssignableFrom(returnType)) return WrapBaseTask;

        // everything else: just wrap the synchronous result.
        return obj => Task.FromResult(obj);
    }

    // A Task is waited and then null is returned.
    // questionable, but it's ok for my scenario.
    private static async Task<object> WrapBaseTask(object obj)
    {
        var task = (Task) obj;
        if (task == null) throw new InvalidOperationException("The returned Task instance cannot be null.");
        await task;
        return null;
    }

    /// <summary> This method just returns a func that awaits for the typed task to complete
    /// and returns the result as a boxed object. </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static Func<object, Task<object>> WrapTypedTask<T>()
    {
        return async obj => await (Task<T>)obj;
    }

    private static readonly Type TypeOfTask = typeof(Task<>);

    /// <summary> Returns true if the provided type is a Task&lt;T&gt; or
    /// extends it. </summary>
    /// <param name="type"></param>
    /// <param name="taskResultType">The type of the result of the Task.</param>
    /// <returns></returns>
    public static bool IsATaskOfT(Type type, out Type taskResultType)
    {
        while (type != null)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == TypeOfTask)
            {
                taskResultType = type.GetGenericArguments()[0];
                return true;
            }

            type = type.BaseType;
        }

        taskResultType = null;
        return false;
    }