我正在使用C#的工具包languageext包,并且当Right值是某种Task时遇到了Either类的问题。出于某种原因,这会导致挂起:
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
来电都挂了。
有没有人知道可能导致此问题或该怎么办?
答案 0 :(得分:1)
最有可能的情况是,您的环境具有同步上下文,并且调用Result
或Wait
几乎总是会死锁。
我不知道那个库做了什么,但这可能有用:
var res = (await repo.GetAccountWithID(accountID)
.Map(c => filesServiceCustomer.Initialize(c)))
.Bind(t => t);
答案 1 :(得分:1)
(我是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.*
)是使用Sequence
翻转内部和外部monad:
var res = task.Sequence();
这会将Task<Either<Exception, bool>>
转换为您可以匹配的Either<Exception, Task<bool>>
。显然,这取决于你的用例,哪些是最合适的。
答案 2 :(得分:0)
关于我发现的和正在发生的事情的更多背景。我设法“修复”了问题,虽然我不确定究竟是什么导致它或为什么需要修复。
首先,这是在Azure API应用服务中运行。不确定这是否有所不同,但它包含在内以保证完整性。
在Initialize
函数中,最后有两行看起来像这样:
rootDir = someShare.GetRoodDirectoryReference();
...
dir1 = rootDir.GetDirectoryReference(dir1Name);
await dir1.CreateIfNotExistsAsync();
dir2 = rootDir.GetDirectoryReference(dir2Name);
await dir2.CreateIfNotExistsAsync();
代码挂在第一个await
电话的CreateIfNotExistAsync()
上(无论哪个位于该位置,都没关系)。但是,我将其更改为:
dir1 = rootDir.GetDirectoryReference(dir1Name);
dir2 = rootDir.GetDirectoryReference(dir2Name);
Task<bool> tasks = {
Task.Run(dir1.CreateIfNotExistAsync),
Task.Run(dir2.CreateIfNotExistAsync),
};
Task.WaitAll(tasks);
而且,就像魔法一样,不再挂了!
现在我的调用代码按预期工作。我不知道为什么需要这个修复。我唯一能想到的是await
语句创建的延续在某种程度上导致了问题。但是,如果我不需要,我真的不想深入研究编译器生成的延续的内容。