本文介绍了获取Func< object>的结果当对象是Task< Something>时,的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我当前正在使用此代码尝试动态执行保存的 Func< object>

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)();
}

如果有人存储了 Func< Task< object> > Func< [anything]> 可以正常工作。但是,如果有人存储了 Func< Task< string>> (或Task中的任何其他通用参数),则会损坏。

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.

无法转换类型为Func< Task< System.String>>的对象。键入Func< Task< System.Object>>

我的问题是:我如何等待此时 Func< Task< Something>> 的结果,然后将该值作为 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?

完整测试代码:

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)();
        }
    }
}


推荐答案

我不清楚您的意图是什么,因为代码不清楚。您正在寻找返回类型的 GetAwaiter()方法,但是当然还有 Task 以外的其他类型有这种方法。此外,您的代码将丢失可通过扩展方法等待的内容。

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.

如果要假定函数返回任务对象(代码当前正在执行) ,那么您应该只检查它,而不要使用 GetAwaiter()方法。相反,您应该只动态地调用 GetAwaiter()方法,以便使用该方法容纳所有内容。

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.

就个人而言,如果不经常调用此代码,我将使用 dynamic ,尝试调用 GetAwaiter(),如果失败则捕获异常(因为该方法不存在),然后直接调用委托。如果性能很重要,则您可以记住等待类型状态,以便在单击一次异常后可以跳过该异常。请注意,使用 dynamic ,您将适应大多数等待的情况(它仍然找不到扩展方法 GetAwaiter() s)。

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).

以下是一个示例:

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

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;
}

这里, Task的类型检查代替哈希集。同样, dynamic 运行时支持将为使用此方法的每种任务类型的 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.

最后,请注意,如果您有 Func< Task> ,则上述方法将无效,因为它假定所有 Task 对象都具有有效结果。有人可能会争辩说,鉴于模棱两可,最好一开始就不要在字典中填入类似内容。但是,如果考虑到这种情况,则可以对上述内容进行修改以解决这种可能性:

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;
}

不幸的是,因为在某些情况下,编译器使用 Task< ; VoidTaskResult> 而不是非通用的 Task 类型,仅检查 Task 。另外,由于 VoidTaskResult 不是公共类型,因此代码必须检查类型名称是否为 string 值。而不是 typeof(Task< VoidTaskResult>)。所以,这有点尴尬。但是它将解决以下情况:返回的东西是 Task 本身,而不是任务的结果。

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.

当然, GetAwaiter()方法没有这个问题。因此,如果这确实令人担忧,那将是选择 GetAwaiter()方法而不是 is 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.

这篇关于获取Func&lt; object&gt;的结果当对象是Task&lt; Something&gt;时,的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-23 14:34