我当前正在使用此代码尝试动态执行已保存的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)();
}
如果有人存储Func<Task<object>>
或Func<[anything]>
,则此代码可以正常工作。但是,如果有人存储了Func<Task<string>>
(或“任务”中的任何其他通用参数),它将中断。
Unable to cast object of type Func<Task<System.String>> to type Func<Task<System.Object>>
我的问题是:我现在如何等待Func<Task<Something>>
的结果并将其值返回为object
?
完整测试代码:
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)();
}
}
}
答案 0 :(得分:2)
我不清楚您的意图是什么,因为代码不清楚。您正在寻找一种关于返回类型的GetAwaiter()
方法,但是当然还有Task
以外的其他类型都具有此方法。而且,您的代码将丢失通过扩展方法可以等待的东西。
如果要假设函数返回一个任务对象(代码当前正在执行),则应检查该对象而不是GetAwaiter()
方法。相反,您应该只动态地调用GetAwaiter()
方法,以便容纳该方法中的所有内容。
就个人而言,如果此代码不会经常被调用,我将使用dynamic
,尝试调用GetAwaiter()
,如果失败则捕获异常(因为该方法不存在) ),然后直接调用委托。如果性能很重要,则可以记住类型输入等待者的状态,以便在单击一次异常之后可以跳过该异常。请注意,使用dynamic
,您将适应大多数等待的情况(它仍然找不到扩展方法GetAwaiter()
)。
这是一个例子:
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;
}
这应该做您想要的,并且应该表现出色。 dynamic
运行时支持已经缓存了可等待场景的解决方案,并且通过将不可等待的MethodInfo
实例存储在哈希集中,代码避免了RuntimeBinderException
多次遭受任何给定的委托目标方法。
一旦代码“热身”(即,在后续遍历中已被调用),它就不应成为瓶颈。
请注意,以上实现假定您不是使用多播委托。在上下文中,这似乎是一个合理的假设,因为不存在对等待的多播委托的内置语言支持(或者说,它将起作用,但是运行时中没有任何方法可以解决关于哪个的歧义等待)。但是,如果您确实需要,当然可以将以上内容扩展为支持多播委托。
如果您不关心仅支持基于Task
的所有情况,则可以将上述内容简化如下:
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;
}
在这里,使用Task
的类型检查代替哈希集。同样,dynamic
运行时支持将为使用此方法的每种类型的任务缓存Result
访问器,因此一旦预热,其性能将与其他任何面向动态的解决方案一样好。
最后,请注意,如果您有Func<Task>
,则上面的命令将无效,因为它假定所有Task
对象都具有有效结果。有人可能会争辩说,鉴于模棱两可,最好一开始就不要在字典中填入类似内容。但是,如果考虑到这种情况,则可以对上述内容进行修改以解决这种可能性:
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;
}
不幸的是,由于在某些情况下,编译器使用Task<VoidTaskResult>
而不是非通用的Task
类型,仅检查Task
是不够的。另外,由于VoidTaskResult
不是公共类型,因此代码必须将类型名称作为string
值而不是typeof(Task<VoidTaskResult>)
进行检查。所以,这有点尴尬。但这将解决以下情况:返回的内容是Task
本身,而不是任务的结果。
当然,GetAwaiter()
方法没有这个问题。因此,如果这确实令人担忧,那将是选择GetAwaiter()
方法而不是is Task
方法的原因之一。