当对象是 Task<Something> 时获取 Func<object> 的结果
Get the result of Func<object> when object is a Task<Something>
我目前正在使用此代码尝试动态执行保存的 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>>
的结果,并且 return 该值作为 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)();
}
}
}
我不完全清楚你的意图是什么,因为代码不明确。您正在寻找 return 类型的 GetAwaiter()
方法,但当然还有 Task
以外的类型具有此方法。此外,您的代码将遗漏扩展方法可等待的内容。
如果您打算假设函数 return 是一个任务对象(代码当前这样做),那么您应该只检查它而不是 GetAwaiter()
方法。相反,您应该只动态调用 GetAwaiter()
方法,以便容纳任何与该方法相关的内容。
就个人而言,如果不会经常调用此代码,我会使用 dynamic
,尝试调用 GetAwaiter()
,如果失败则捕获异常(因为该方法不是' t present)并直接调用委托。如果 perf 很重要,您可以记住 type-to-awaiter 状态,以便在您命中一次后可以跳过异常。请注意,使用 dynamic
,您将适应大多数可等待的场景(它仍然找不到扩展方法 GetAwaiter()
s)。
这是一个例子:
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
任何给定的委托目标方法。
一旦代码 "warmed up"(即已按照后续传递的方式调用),它就不会成为瓶颈。
请注意,上面的实现假定您不使用多播委托。鉴于上下文,这似乎是一个合理的假设,因为没有内置语言支持可等待的多播委托(或者更确切地说,它会起作用,但运行时没有任何东西可以解决 which[=66 的歧义=]等待等待)。但是如果你真的需要的话,你当然可以扩展上面的内容来支持多播委托。
如果您不关心支持所有可等待的场景,而只关心基于 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
不是 public 类型,代码必须检查类型名称作为 string
值而不是 typeof(Task<VoidTaskResult>)
。所以,有点尴尬。但它会解决被 returned 的东西是 Task
本身,而不是任务结果的场景。
当然,GetAwaiter()
方法没有这个问题。因此,如果这确实值得关注,那将是选择 GetAwaiter()
方法而不是 is Task
方法的原因之一。
我目前正在使用此代码尝试动态执行保存的 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>>
的结果,并且 return 该值作为 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)();
}
}
}
我不完全清楚你的意图是什么,因为代码不明确。您正在寻找 return 类型的 GetAwaiter()
方法,但当然还有 Task
以外的类型具有此方法。此外,您的代码将遗漏扩展方法可等待的内容。
如果您打算假设函数 return 是一个任务对象(代码当前这样做),那么您应该只检查它而不是 GetAwaiter()
方法。相反,您应该只动态调用 GetAwaiter()
方法,以便容纳任何与该方法相关的内容。
就个人而言,如果不会经常调用此代码,我会使用 dynamic
,尝试调用 GetAwaiter()
,如果失败则捕获异常(因为该方法不是' t present)并直接调用委托。如果 perf 很重要,您可以记住 type-to-awaiter 状态,以便在您命中一次后可以跳过异常。请注意,使用 dynamic
,您将适应大多数可等待的场景(它仍然找不到扩展方法 GetAwaiter()
s)。
这是一个例子:
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
任何给定的委托目标方法。
一旦代码 "warmed up"(即已按照后续传递的方式调用),它就不会成为瓶颈。
请注意,上面的实现假定您不使用多播委托。鉴于上下文,这似乎是一个合理的假设,因为没有内置语言支持可等待的多播委托(或者更确切地说,它会起作用,但运行时没有任何东西可以解决 which[=66 的歧义=]等待等待)。但是如果你真的需要的话,你当然可以扩展上面的内容来支持多播委托。
如果您不关心支持所有可等待的场景,而只关心基于 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
不是 public 类型,代码必须检查类型名称作为 string
值而不是 typeof(Task<VoidTaskResult>)
。所以,有点尴尬。但它会解决被 returned 的东西是 Task
本身,而不是任务结果的场景。
当然,GetAwaiter()
方法没有这个问题。因此,如果这确实值得关注,那将是选择 GetAwaiter()
方法而不是 is Task
方法的原因之一。