【问题标题】:How to bind a function with arguments into a package in C#如何将带参数的函数绑定到 C# 中的包中
【发布时间】:2018-08-28 12:47:03
【问题描述】:

我正在使用 C# 编写线程池。

这个线程池需要支持执行不同类型的函数。

以下是我需要的:

  1. 将函数及其参数绑定到函子(或称为可调用对象)中,其签名应为 void(void)
  2. 获取一个System.Threading.Tasks< TResult >,它代表我们的可调用包的返回值

在 CPP 中,我可以通过一些模板魔法来做到这一点:

template <typename funtype, typename ...argstype>
std::future<typename std::result_of<funtype(argstype...)>::type> async(funtype&& func, argstype&&... args) {
    //start function body↓

    typedef std::packaged_task<std::result_of<funtype(argstype...)>::type(argstype...)> task_type;
    auto task = std::make_shared<task_type>(std::forward<funtype>(func));

    // bind to a callable object(functor) with signature void(void)
    auto whatINeed= std::bind([task](argstype... args) mutable {
    (*task)(std::forward<argstype>(args)...);
    }, std::forward<argstype>(args)...);

    //and we return the std::future which represents the return value of our package
    //in C#, i need to return an Task<TResult>
    return task->get_future();
}

在 C# 中,现在我写道:

public Task<TResult> async<TResult>(Delegate func, params object[] args)
{
    var stdPromiseXD = new TaskCompletionSource<TResult>();
    // the lambda is a callable object with signature void(void)
    works.Enqueue(() =>
    {
        try
        {
            stdPromiseXD.SetResult((TResult)func.DynamicInvoke(args));
        }
        catch (Exception ex)
        {
            stdPromiseXD.SetException(ex);
        }
    });
    // return the Task which equals std::future in CPP
    return stdPromiseXD.Task;
}

但是这个C#版本不如CPP版本好。首先它不支持不返回函数,其次 DynamicInvoke 方法在某些情况下可能会非常慢。

那么谁能告诉我如何在 C# 中更优雅地将函数和参数绑定到一个包中?

【问题讨论】:

  • 我的第一个问题可能是你为什么要自己写一个线程池? .Net Tasks 有什么不能做的吗?您可能还考虑执行“一直异步”,如下所示:您传入的委托可能知道异步模式。 (因为取消是一项合作任务,让 CancellationToken 等待它很方便)。您也可以在(高阶函数)之前传入函数参数,然后只传入带有捕获参数的函数。线程池库对参数没有做任何事情,所以它一定没有意识到这一点,对吧?
  • 在给定约束的情况下给出一个快速的答案:您可能需要一些带有泛型参数的重载。 (async 通常在函数的返回类型之前出现,而你的函数还没有名称?)
  • @DennisKuypers 只是为了好玩,我知道 dotnet 为每个程序提供一个线程池
  • @DennisKuypers 重载....我知道我们可以使用重载,但我认为这并不优雅。我想使用一些漂亮的东西,比如 C++11 中的模板可变长度参数
  • 我不知道任何 C# 类型安全的 C++-variadic-style 函数机制。如果您查看此action.cs 源代码,您还会发现各种重载,我假设Func 的作者可能使用过类似的东西。 (当然,它们是委托,而不是函数声明)

标签: c# c++ templates generics


【解决方案1】:

我建议使用Func&lt;TResult&gt;Action 而不是委托,然后在调用代码中使用闭包来简化使用。 Func 是一个返回结果的泛型强类型委托,而 Action 是一个不返回结果的泛型强类型委托。

public Task<TResult> Enqueue<TResult>(Func<TResult> func)
{
    var stdPromiseXD = new TaskCompletionSource<TResult>();
    // the lambda is a callable object with signature void(void)
    works.Enqueue(() =>
    {
        try
        {
            stdPromiseXD.SetResult((TResult)func());
        }
        catch (Exception ex)
        {
            stdPromiseXD.SetException(ex);
        }
    });
    // return the Task which equals std::future in CPP
    return stdPromiseXD.Task;
}

public Task Enqueue(Action action)
{
    return Enqueue<object>(() =>
    {
        action();
        return null;
    });
}

我这样称呼它:

var arg1 = "x1";
var arg2 = "2nd";
var arg3 = "third";

var resultTask1 = tp.Enqueue(() => DoConsoleWrite(arg1, arg2, arg3));
var resultTask2 = tp.Enqueue(() => SumAllNumbers(1, 2, 3, 4, 5));
var resultTask3 = tp.Enqueue(() => ThrowException());

while (tp.Pop()) { }

resultTask1.GetAwaiter().GetResult();
var result2 = resultTask2.GetAwaiter().GetResult();
var result3Exception = resultTask3.Exception;

使用闭包的替代方法是为 func 的每个参数计数创建重载(Func&lt;TResult&gt;, Func&lt;T1,TResult&gt;, Func&lt;T1,T2,Result&gt;, etcAction, Action&lt;T1&gt;, Action&lt;T1,T2&gt;, etc

【讨论】:

  • 不客气。如果您认为这是您问题的答案,请单击复选标记以指定它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-18
  • 2023-03-14
  • 1970-01-01
  • 2018-02-07
  • 2011-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多