【发布时间】:2019-10-03 21:35:48
【问题描述】:
注意 1:这里 CPS 代表"continuation passing style"
我对了解如何连接到 C# 异步机制非常感兴趣。 基本上,据我了解 C# async/await 功能,编译器正在执行 CPS 转换,然后将转换后的代码传递给一个上下文对象,该对象管理各种线程上的任务调度。
您认为可以利用该编译器功能来创建 强大的组合器,同时抛开默认的线程方面?
一个例子是可以去递归和记忆像
这样的方法async MyTask<BigInteger> Fib(int n) // hypothetical example
{
if (n <= 1) return n;
return await Fib(n-1) + await Fib(n-2);
}
我设法做到了:
void Fib(int n, Action<BigInteger> Ret, Action<int, Action<BigInteger>> Rec)
{
if (n <= 1) Ret(n);
else Rec(n-1, x => Rec(n-2, y => Ret(x + y)));
}
(不使用异步,非常笨拙...)
或使用monad (While<X> = Either<X, While<X>>)
While<X> Fib(int n) => n <= 1 ?
While.Return((BigInteger) n) :
from x in Fib(n-1)
from y in Fib(n-2)
select x + y;
稍微好一点,但不像异步语法那么可爱:)
我在the blog of E. Lippert 上问过这个问题,他很友好地告诉我这确实是可能的。
在实现 ZBDD 库时需要我:(一种特殊的 DAG)
大量复杂的相互递归操作
实际示例中堆栈不断溢出
只有在完全记忆的情况下才实用
手动 CPS 和去递归非常繁琐且容易出错。
对我所追求的东西(堆栈安全性)的严格测试是这样的:
async MyTask<BigInteger> Fib(int n, BigInteger a, BigInteger b)
{
if (n == 0) return b;
if (n == 1) return a;
return await Fib(n - 1, a + b, a);
}
默认行为会在Fib(10000, 1, 0) 上产生堆栈溢出。或者更好的是,使用开头的代码和 memoization 来计算 Fib(10000)。
【问题讨论】:
-
IEnumerable<T>/IEnumerator<T>和yield不能满足您的需求吗?它实际上是 async/await 的解耦机制。 -
有可能:
IEnumerator<T>在概念上类似于Maybe[(T, IEnumerator<T>)],尽管是有状态的。我还提到了一个 monad 构造While<X> = Either<X, While<X>>可以解决问题,但实际上我的问题是关于劫持编译器在 async/await 语句上执行的 CPS 转换。 -
已经可以通过使用自定义等待程序扩展此机制而无需进入编译器级别。至于记忆,我有严重的怀疑,因为
async语义并不暗示它。 -
@DmytroMukalov 读到 link 似乎很有希望,尤其是那个:
Await task1.OnCoroutine(crm1) -
我知道,但请注意 F# 的计算表达式比 C# 对查询理解所做的更灵活,因为这些模式是固定的,而 F# 允许添加你自己的 -- 加上 F# 的类型和泛型使“做事”通常更容易发挥作用。我不争辩你可以用 C# 做这种事情,而且这个问题本身是有效的,但退后一步考虑你是否使用了正确的工具永远不会除了纯粹的智力练习之外,这也是一件坏事。我特别提到了 F#,因为 C# 和 F# 可以很容易地混合使用,具有 .NET 的共同点。
标签: c# async-await stack-overflow combinators continuation-passing