Using IDisposable with TryAsync and TryOptionAsync
See original GitHub issueI’m currently working on my first end-to-end C# greenfield project with language-ext and so far it’s been great!
One issue that couldn’t figure out by myself is the use of use
function as the replacement to the standard using(...) { }
. Consider a standard DB access method in ASP.NET:
public async Task<int> MaxUserAge()
{
using (var ctx = new DbContext()) {
return await ctx.Users.Select(u => u.Age).MaxAsync();
}
}
I started refactoring methods of that kind to return type of TryOptionAsync
. However, it made it really difficult to apply use
function that takes IDisposable -> Task<T>
to prevent dispose of the DB connection earlier than needed.
I see two possible solutions for the problem: either return TryOptionAsync
but use using
pattern or return Task<Option<T>>
which requires using match
inside of use
function.
It seems to me that ...Async
monads and Task
are not as easily interchangeable as I expected. Did I assume wrong that the library encourages TryAsync
and TryOptionAsync
as return values or should those only be used as wrapper monads inside of other functions?
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (6 by maintainers)
Top GitHub Comments
These extensions might help you get closer to what you’re after:
You should then be able to do:
Or,
Depending on your style preference.
I will probably add them to lang-ext soon, because it’s a glaring gap with the existing
use
functions. But, I’m also wary of just sticking them in, so drop these in your project for now, and deprecate them when they land in lang-ext.@iamim Your functions are basically how you have to do it (without #567), but I’ll step through a refactoring to explain why.
The challenge is that
TryAsync
has two kinds of deferral that you have to wait for before cleaning up the resource:Starting with this:
let’s first remove a bit a sugar for clarity
Now it’s clear that we’ve closed over
a
in our thunk. This reference toa
is problematic becausea
is disposed immediately after the thunk is returned, which means is not longer available when the thunk is actually evaluated (viaMatch
orIfFailThrow
, etc).Here’s and idea, let’s move
a
out of our thunk to prevent that closureand instead close over
taskB
. This change moves us into problem 2 becausetaskB
might still be working when wereturn
, which means we might disposea
beforetaskB
is done with it.So let’s make sure we
await
taskB
before wereturn
:From here we could reach @iamim 's implementation by inlining the
Bind
call.So the lesson is that you should always put the resource disposal (
using
ortry/finally
) inside theTryAsyc
factory call rather than call theTryAsync
factory from insideusing
ortry/finally
blocks.Dealing with resources and the two kinds of deferral in
TryAsync
can be quite tricky and also puts you back into imperative land with all theawait
ing. Stay tuned to #567 for a better story around deferred structures and resource disposal.