问题
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