当对象是 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 方法的原因之一。