languageext Either.Map/Bind with a Task in the Right positio



我正在使用用于 C# 的工具包 languageext 包,当 Right 值是某种任务时,我遇到了 Any 类的问题。 由于某种原因,这会导致挂起:

var res = repo.GetAccountWithID(accountID)
.Map(c => filesServiceCustomer.Initialize(c))
.Bind(t => t.Result);

在这里,GetAccountWithID返回一个Either<Exception, Account>Initialize方法获取一个Account并返回一个Task<Either<Exception, bool>>。 但是,MapBind呼叫似乎挂起。

有没有人知道可能导致这种情况的原因或该怎么做?

(我是language-ext项目的作者)。 除了Task本身阻塞之外,您的表达式挂起没有根本原因 -MapBind是微不足道的函数,不会做任何特别聪明的事情,绝对不会做任何同步或类似的事情。 我刚刚将此代码添加到 lang-ext 中的单元测试中,它返回正常:

public class Account : NewType<Account, Unit>
{
public Account(Unit _) : base(unit) { }
}
Either<Exception, Account> GetAccountWithID(int accountId) =>
Account.New(unit);
Task<Either<Exception, bool>> Initialize(Account c) =>
Task.FromResult(Right<Exception, bool>(true));
[Fact]
public void StackOverflowQuestion()
{
int accountID = 0;
var res = GetAccountWithID(accountID)
.Map(c => Initialize(c))
.Bind(t => t.Result);
}

值得一提的一件事是,在一项任务上打电话给.Result并不是很好的做法。 您绝对可以利用 language-ext 中的其他功能来更好地为您工作:

例如:

var task = from c in GetAccountWithID(accountID).AsTask()
from r in Initialize(c)
select r;

AsTaskEither<Exception, Account>提升到Task<Either<Exception, Account>>中,这意味着它可以在带有Initialize的LINQ表达式中使用(这也返回一个Task)。

如果您从根本上反对 LINQ 语法,则可以执行以下操作:

var task = GetAccountWithID(accountID).AsTask().BindT(Initialize);

然后task是一个Task<Either<Exception, bool>>,您可以await

var res = (await task).IfLeft(false);

另一个技巧(如果您使用的是版本2.0.*)是使用翻转内部和外部 monads 的Sequence

var res = task.Sequence();

这会将Task<Either<Exception, bool>>变成您可以匹配的Either<Exception, Task<bool>>。 显然,这取决于您的用例,什么是最合适的。

最有可能的是,您的环境具有同步上下文,调用ResultWait几乎总是会死锁。

我不知道该库是做什么的,但这可能会起作用:

var res = (await repo.GetAccountWithID(accountID)
.Map(c => filesServiceCustomer.Initialize(c)))
.Bind(t => t);

这里有更多关于我发现的内容和正在发生的事情的背景。 我设法"修复"了这个问题,尽管我不确定究竟是什么原因造成的,或者为什么需要修复。

首先,这是在 Azure API 应用服务中运行的。 不确定这是否有所不同,但为了完整性而包含它。

Initialize函数内部,末尾有两行如下所示:

rootDir = someShare.GetRoodDirectoryReference();
...
dir1 = rootDir.GetDirectoryReference(dir1Name);
await dir1.CreateIfNotExistsAsync();
dir2 = rootDir.GetDirectoryReference(dir2Name);
await dir2.CreateIfNotExistsAsync();

代码挂在第一个CreateIfNotExistAsync()调用的await上(无论哪个位置,都没关系)。 但是,我将其更改为:

dir1 = rootDir.GetDirectoryReference(dir1Name);
dir2 = rootDir.GetDirectoryReference(dir2Name);
Task<bool> tasks = {
Task.Run(dir1.CreateIfNotExistAsync),
Task.Run(dir2.CreateIfNotExistAsync),
};
Task.WaitAll(tasks);

而且,就像变魔术一样,不再挂!

现在我的调用代码按预期工作。 我不知道为什么需要此修复程序。 我唯一能想到的是,await语句创建的延续以某种方式导致了问题。 不过,如果没有必要的话,我真的不想深入研究编译器生成的延续的内脏。

最新更新