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