Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert Task<object> to Task<T> where T is unknown

How can i convert Task<object> to Task<T> where T is unknown?

For example Task<object> to Task<int>

interface IAsyncActions
{
    Task<int> GetValueAsync();
}

class Proxy : RealProxy
{
    public Proxy() : base(typeof(IAsyncActions)) { }

    public override IMessage Invoke(IMessage msg)
    {
        var call = (IMethodCallMessage)msg;
        var method = (MethodInfo)call.MethodBase;
        if (method.ReturnType.IsSubclassOf(typeof(Task)))
        {
            // return type is Task<T>
            Task taskResult = AsyncMethod2(call);
            return new ReturnMessage(taskResult, null, 0, call.LogicalCallContext, call); // InvalidCastException if taskResult not a Task<int>
        }
        else
        {
            // ...
            return null;
        }
    }

    static void Main()
    {
        Proxy p = new Proxy();
        IAsyncActions tProxy = (IAsyncActions)p.GetTransparentProxy();
        int result = tProxy.GetValueAsync().Result; // InvalidCastException
    }

    // This method works fine
    Task AsyncMethod(IMethodCallMessage call)
    {
        Task<int> task = Task.FromResult(1234);
        return task;
    }

    // This method does not work
    Task AsyncMethod2(IMethodCallMessage call)
    {
        Type taskReturnType = ((MethodInfo)call.MethodBase).ReturnType; // Task<int>

        Task<object> result = Task.FromResult<object>(1234);

        // converting result to taskReturnType
        // ...
        //

        return result;
    }
}

I found one solution but it is quite expensive:

Task AsyncMethod2(IMethodCallMessage call, MethodInfo method)
{
    PropertyInfo resultProp = method.ReturnType.GetProperty("Result");
    Type taskResultType = resultProp.PropertyType;
    Type tcs = typeof(TaskCompletionSource<>);
    Type[] typeArgs = { taskResultType };
    Type genericTcs = tcs.MakeGenericType(typeArgs);
    var taskProperty = genericTcs.GetProperty("Task");
    object tcsInstance = Activator.CreateInstance(genericTcs);
    MethodInfo setResult = genericTcs.GetMethod("SetResult");
    MethodInfo setException = genericTcs.GetMethod("SetException", new Type[] { typeof(IEnumerable<Exception>)});
    var setEx = (Action< IEnumerable<Exception>>)setException.CreateDelegate(typeof(Action<IEnumerable<Exception>>), tcsInstance);
    Task task = (Task)taskProperty.GetValue(tcsInstance);

    Task<object> result = new Task<object>(delegate 
    {
        //throw new InvalidOperationException("qwerty");
        return 1234;
    });

    result.Start();

    result.ContinueWith(x =>
    {
        var args = new object[] { x.Result };
        setResult.Invoke(tcsInstance, args);
    }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion);

    result.ContinueWith(x =>
    {
        setEx(x.Exception.InnerExceptions);
    }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted);

    return task;
}
like image 561
Miles Avatar asked Jan 01 '16 23:01

Miles


1 Answers

Create this method:

public async static Task<T> Convert<T>(Task<object> task)
{
    var result = await task;

    return (T) result;
}

And then you can do something like this (this is needed because T is only known at runtime):

//Assuming that variable "task" is referencing the Task<object>

Type taskReturnType = ((MethodInfo) call.MethodBase).ReturnType; //e.g. Task<int>

var type = taskReturnType.GetGenericArguments()[0]; //get the result type, e.g. int

var convert_method = this.GetType().GetMethod("Convert").MakeGenericMethod(type); //Get the closed version of the Convert method, e.g. Convert<int>

var result = convert_method.Invoke(null, new object[] {task}); //Call the convert method and return the generic Task, e.g. Task<int>
like image 126
Yacoub Massad Avatar answered Sep 29 '22 11:09

Yacoub Massad