问题
I am currently using this code to attempt to dynamically execute a saved Func<object>
:
public async Task<object> GetFuncResult(string funcName) {
Func<object> func = _savedFuncs[funcName];
bool isAwaitable = func.Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
if (!isAwaitable) return func();
else return await ((Func<Task<object>>)func)();
}
If somebody stores a Func<Task<object>>
or a Func<[anything]>
this code works fine. But if somebody stores a Func<Task<string>>
(or any other generic argument in the Task), it breaks.
Unable to cast object of type Func<Task<System.String>> to type Func<Task<System.Object>>
My question is: How do I await the result of the Func<Task<Something>>
at this point and just return that value as an object
?
Full Test Code:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace TestConsole
{
class Program
{
static Dictionary<string, Func<object>> _savedFuncs;
static async Task Main(string[] args)
{
_savedFuncs = new Dictionary<string, Func<object>>();
Func<Task<string>> myTask = async () => { return "Test Success"; };
_savedFuncs.Add("myFunc", myTask);
Console.WriteLine((await GetFuncResult("myFunc")) ?? "No Value Returned");
Console.ReadKey();
}
public static async Task<object> GetFuncResult(string funcName)
{
Func<object> func = _savedFuncs[funcName];
bool isAwaitable = func.Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
if (!isAwaitable) return func();
return await ((Func<Task<object>>)func)();
}
}
}
回答1:
I'm not entirely clear on what your intent here is, because the code is not clear. You are looking for a GetAwaiter()
method on the return type, but of course there are types other than Task
which have this method. Also, your code will miss things that are awaitable by virtue of extension method.
If you are going to assume the function returns a task object (which the code currently does), then you should just check for that instead of the GetAwaiter()
method. Conversely, you should just invoke the GetAwaiter()
method dynamically, so that anything with the method is accommodated.
Personally, if this code isn't going to be invoked very often, I would use dynamic
, try to call GetAwaiter()
, catch the exception if that fails (because the method isn't present) and just invoke the delegate directly. If perf is important, you can memoize the type-to-awaiter status so that the exception can be skipped after you hit it once. Note that using dynamic
, you'll accommodate most awaitable scenarios (it still won't find extension-method GetAwaiter()
s).
Here is an example of that:
private static readonly HashSet<MethodInfo> _notAwaitable = new HashSet<MethodInfo>();
public static async Task<object> GetFuncResult(string funcName)
{
Func<object> func = _savedFuncs[funcName];
dynamic result = func();
if (!_notAwaitable.Contains(func.Method))
{
try
{
return await result;
}
catch (RuntimeBinderException) { } // not awaitable
_notAwaitable.Add(func.Method);
}
return result;
}
This should do what you want, and should also be performant. The dynamic
runtime support already caches the resolution of the awaitable scenario, and by storing the non-awaitable MethodInfo
instances in the hash set, the code avoids ever suffering the RuntimeBinderException
more than once for any given delegate target method.
Once the code's "warmed up" (i.e. has been invoked in the way it will on subsequent passes), it should not be a bottleneck.
Note that the implementation above assumes you are not using multicast delegates. Given the context, that seems like a reasonable assumption, since there's no built-in language support for awaitable multicast delegates (or rather, it will work, but nothing in the runtime resolves the ambiguity as to which awaitable is awaited). But you could of course extend the above to support multicast delegates if you really needed that.
If you don't care about supporting all awaitable scenarios, but only Task
-based ones, you can simplify the above a bit like this:
public static async Task<object> GetFuncResult(string funcName)
{
Func<object> func = _savedFuncs[funcName];
object result = func();
if (result is Task task)
{
await task;
return ((dynamic)task).Result;
}
return result;
}
Here, the type-check for Task
is used in lieu of the hash set. Again, the dynamic
runtime support will cache the Result
accessor for each type of task used with this method, so once warmed up will perform just as well as any other dynamically-oriented solution.
Finally, note that if you have a Func<Task>
, the above won't work, because it assumes all Task
objects have a valid result. One might argue that given the ambiguity, it'd be best to not populate the dictionary with anything like that in the first place. But assuming that scenario is of concern, the above can be modified to account for the possibility:
public static async Task<object> GetFuncResult(string funcName)
{
Func<object> func = _savedFuncs[funcName];
object result = func();
if (result is Task task)
{
Type resultType = result.GetType();
// Some non-result task scenarios return Task<VoidTaskResult> instead
// of a plain non-generic Task, so check for both.
if (resultType != typeof(Task) &&
resultType.GenericTypeArguments[0].FullName != "System.Threading.Tasks.VoidTaskResult")
{
await task;
return ((dynamic)task).Result;
}
}
return result;
}
Unfortunately, because in some cases the compiler uses Task<VoidTaskResult>
instead of the non-generic Task
type, it's not sufficient to just check for Task
. In addition, because VoidTaskResult
is not a public type, the code has to check for the type name as a string
value instead of typeof(Task<VoidTaskResult>)
. So, it's a little awkward. But it will address the scenarios where the thing being returned is the Task
itself, not the result of the task.
Of course, the GetAwaiter()
approach doesn't have this issue. So if this was actually of concern, that would be one reason to choose the GetAwaiter()
approach instead of the is Task
approach.
来源:https://stackoverflow.com/questions/59080219/get-the-result-of-funcobject-when-object-is-a-tasksomething