重构 2 个相同的函数,它们分别接收回调 Func<Task<T>> 和 Func<T>

Posted

技术标签:

【中文标题】重构 2 个相同的函数,它们分别接收回调 Func<Task<T>> 和 Func<T>【英文标题】:Refactoring 2 identical functions which receive a callback Func<Task<T>> and Func<T> respectively 【发布时间】:2021-04-05 17:45:32 【问题描述】:
public static async Task<T> NDC<T>(Func<Task<T>> func)

    using (SomeDisposable())
    
        try
        
            return await func();
        
        catch (Exception ex)
        
            Console.WriteLine("error");

            throw;
        
    


public static T NDC<T>(Func<T> func)

    using (SomeDisposable())
    
        try
        
            return func();
        
        catch (Exception ex)
        
            Console.WriteLine("error");

            throw;
        
    

如您所见,这两个功能几乎相同。它们之间的唯一区别是,当回调是Func&lt;Task&lt;T&gt;&gt;时,它会被等待,因此返回类型是Task&lt;T&gt;,否则,函数将返回类型T

我想对这些功能进行分组/通用,有什么方法可以实现吗?提前致谢。

附:如果调用者调用的是非任务版本,则该函数中的所有进程都应该在单个线程中运行。

【问题讨论】:

库作者提供方法的同步和异步变体是有原因的......它们被设计为以不同的方式调用并携带不同的语义。这是对 DRY 的不好使用,最终会导致代码难以维护。 另外,考虑到 IAsyncDisposable 的引入,即使在 using 语句中包装一次性用品也可能不会使用相同的实现。 让我删除它 【参考方案1】:

您可以从异步版本派生同步版本,如下所示:

public static T NDC<T>(Func<T> func)

    if (func == null) throw new ArgumentNullException(nameof(func));
    return NDC(() => Task.FromResult(func())).GetAwaiter().GetResult();

有两个陷阱:

    你不能只用一种方法验证参数。您必须在两个 NDC 方法中验证 func 参数。除非您使用可为空的引用类型,如 cmets 中的 Alexei Levenkov pointed out。

    此解决方案仅在 await func(); 是异步 NDC 方法中唯一等待的操作时才有效。否则当前线程将被阻塞,并且会发生不好的事情(可伸缩性下降或死锁,具体取决于应用程序的类型)。

【讨论】:

看来如果我这样做,我仍然会进入我想避免的多线程情况 @mannok 我看不出并发是如何发生的,除非在 NDC 方法中还有其他等待的操作,而您没有向我们展示。 好点,您可以通过知道异步一个实际上不会使用同步回调运行异步来实际破解同步版本...一对注意事项:对于 C# 9,您有可以为空的引用类型,可以让您跳过它空检查...实际上,由于在这种情况下没有异步执行,您可以只做.Result...(如果NDC除此之外还有实际的异步调用,真的没有充分的理由尝试组合它们) @AlexeiLevenkov 我不是可空引用概念的忠实拥护者。我看到所有这些卷曲的问号作为语言污染无处不在。 :-) 关于使用.Result 属性,这不会在AggregateException 中包含可能的异常吗?

以上是关于重构 2 个相同的函数,它们分别接收回调 Func<Task<T>> 和 Func<T>的主要内容,如果未能解决你的问题,请参考以下文章

php回调函数的概念及实例

PHP伪类型和伪变量

Java精练代码:一次Java函数式编程的重构之旅

14_函数类型和回调函数

回调函数

python3 进程池回调函数