我正在使用用于 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>>
。 但是,Map
或Bind
呼叫似乎挂起。
有没有人知道可能导致这种情况的原因或该怎么做?
(我是language-ext项目的作者)。 除了Task
本身阻塞之外,您的表达式挂起没有根本原因 -Map
和Bind
是微不足道的函数,不会做任何特别聪明的事情,绝对不会做任何同步或类似的事情。 我刚刚将此代码添加到 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;
AsTask
将Either<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>>
。 显然,这取决于您的用例,什么是最合适的。
最有可能的是,您的环境具有同步上下文,调用Result
或Wait
几乎总是会死锁。
我不知道该库是做什么的,但这可能会起作用:
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
语句创建的延续以某种方式导致了问题。 不过,如果没有必要的话,我真的不想深入研究编译器生成的延续的内脏。