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

冷暖自知 提交于 2020-01-07 06:18:47

问题


I am using the toolkit languageext package for C# and am running into a problem with the Either class when the Right value is some kind of Task. For some reason this is causing a hang:

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

Here, GetAccountWithID returns an Either<Exception, Account> and the Initialize method take an Account and returns a Task<Either<Exception, bool>>. However, it would appear that either the Map or Bind calls are hanging.

Does anyone have any idea what might be causing this or what to do about it?


回答1:


(I'm the author of the language-ext project). There's no fundamental reason for your expression to hang, other than if the Task itself is blocking - Map and Bind are trivial functions that don't do anything particularly clever, and definitely don't do any synchronisation or anything like that. I just added this code to the unit tests in lang-ext, and it returns fine:

    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);
    }

One thing it's worth mentioning is that it's not great practice to call .Result on a task. You can definitely leverage other features in language-ext to make this work better for you:

For example:

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

AsTask lifts the Either<Exception, Account> into a Task<Either<Exception, Account>>, which then means it's usable in a LINQ expression with Initialize (which also returns a Task).

If you're fundamentally opposed to the LINQ syntax, then you can do:

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

task is then a Task<Either<Exception, bool>> which you can await:

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

Another trick (if you're using version 2.0.*) is to use Sequence which flips the inner and outer monads:

    var res = task.Sequence();

That will turn the Task<Either<Exception, bool>> into a Either<Exception, Task<bool>> which you can match on. Obviously it depends on your use-case as to what's most appropriate.




回答2:


Most probably, your environment has a synchronization context and calling Result or Wait will almost always deadlock.

I don't know what that library does, but this will probably work:

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



回答3:


A little more context here on what I found and what is happening. I managed to 'fix' the problem though I am not sure exactly what was causing it or why the fix is needed.

First of all this is running in an Azure API app service. Not sure if that makes a difference or not but it is included for completeness.

Inside the Initialize function there were two lines at the end that look like this:

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

dir2 = rootDir.GetDirectoryReference(dir2Name);
await dir2.CreateIfNotExistsAsync();

The code was hanging on the await of the first CreateIfNotExistAsync() call (whichever was in that position, it didn't matter). However, I changed this to:

dir1 = rootDir.GetDirectoryReference(dir1Name);
dir2 = rootDir.GetDirectoryReference(dir2Name);

Task<bool> tasks = {
    Task.Run(dir1.CreateIfNotExistAsync),
    Task.Run(dir2.CreateIfNotExistAsync),
};

Task.WaitAll(tasks);

And, like magic, no more hang!

Now my calling code works as expected. I don't know why this fix is needed. The only thing I can think of is that the continuation created by the await statements were somehow causing a problem. I don't really want to dig into the guts of the compiler-generated continuations if I don't have to, though.



来源:https://stackoverflow.com/questions/43057927/languageext-either-map-bind-with-a-task-in-the-right-position

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!