Reputation: 7360
I'm extending an existing Rpc library to support async methods.
In the current implementation, I'm using factory methods to build typed delegates, like this (I'm leaving out some implementation details, unrelated to the question):
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;
}
};
}
The resulting object
is then passed on to a serialization class, which will handle every case.
Now I want to support also asynchronous methods, that is, methods that return a Task<T>
.
I want to change the signature of BuildServiceMethodInvocation
so that it's a Func<ServiceType, PayloadType, Task<object>>
.
I don't know how to easily wrap every possible value in a Task<object>
: it should handle plain values, but also Task<T>
(boxing into an object).
Any ideas?
Edit:
serviceMethod could return a string, and I want to get a Task<object>
returning the string value.
But serviceMethod could return a Task<int>
, in which case I would like to get back a Task<object>
returning the int value (boxed into an object).
Upvotes: 2
Views: 2527
Reputation: 7360
Ok, putting together the hints by @Evk (and John Skeet, and Marc Gravell) I came up with a solution that seems to work.
The invoke was changed in this way:
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;
}
};
}
The BuildTaskWrapperFunction
is responsible for building a function that will take an object and will return a Task<object>
, actually extracting and reboxing a result as needed.
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<T> 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;
}
Upvotes: 1
Reputation: 77354
The easiest way to make it work syntactically would be Task.FromResult. Now, that doesn't make your method asyncronous. But there is no extra boxing or unboxing involved, at least not more than before.
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;
}
};
}
Upvotes: 4