languageext Either.Map/Bind with Task in the right position

时间:2017-03-27 23:10:41

标签: c# functional-programming async-await

我正在使用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>>。但是,似乎MapBind来电都挂了。

有没有人知道可能导致此问题或该怎么办?

3 个答案:

答案 0 :(得分:1)

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

我不知道那个库做了什么,但这可能有用:

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

答案 1 :(得分:1)

(我是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.*)是使用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语句创建的延续在某种程度上导致了问题。但是,如果我不需要,我真的不想深入研究编译器生成的延续的内容。