question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Using IDisposable with TryAsync and TryOptionAsync

See original GitHub issue

I’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:closed
  • Created 4 years ago
  • Comments:7 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
louthycommented, May 9, 2019

These extensions might help you get closer to what you’re after:

public static class TryAsyncExt
{
    public static TryAsync<B> Use<A, B>(this TryAsync<A> ma, Func<A, B> f) where A : IDisposable =>
        use(ma, f);

    public static TryAsync<B> Use<A, B>(this TryAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        use(ma, f);

    public static TryAsync<B> Use<A, B>(this TryAsync<A> ma, Func<A, TryAsync<B>> f) where A : IDisposable =>
        use(ma, f);

    public static TryAsync<B> use<A, B>(TryAsync<A> ma, Func<A, B> f) where A : IDisposable =>
        ma.Map(a => Prelude.use(a, f));

    public static TryAsync<B> use<A, B>(TryAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        ma.Bind(a =>
        {
            try
            {
                return TryAsync(f(a));
            }
            finally
            {
                a?.Dispose();
            }
        });

    public static TryAsync<B> use<A, B>(TryAsync<A> ma, Func<A, TryAsync<B>> f) where A : IDisposable =>
        ma.Bind(a =>
        {
            try
            {
                return f(a);
            }
            finally
            {
                a?.Dispose();
            }
        });
}

public static class TryOptionAsyncExt
{
    public static TryOptionAsync<B> Use<A, B>(this TryOptionAsync<A> ma, Func<A, B> f) where A : IDisposable =>
        use(ma, f);

    public static TryOptionAsync<B> Use<A, B>(this TryOptionAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        use(ma, f);

    public static TryOptionAsync<B> Use<A, B>(this TryOptionAsync<A> ma, Func<A, TryOptionAsync<B>> f) where A : IDisposable =>
        use(ma, f);

    public static TryOptionAsync<B> use<A, B>(TryOptionAsync<A> ma, Func<A, B> f) where A : IDisposable =>
        ma.Map(a => Prelude.use(a, f));

    public static TryOptionAsync<B> use<A, B>(TryOptionAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        ma.Bind(a =>
        {
            try
            {
                return TryOptionAsync(f(a));
            }
            finally
            {
                a?.Dispose();
            }
        });

    public static TryOptionAsync<B> use<A, B>(TryOptionAsync<A> ma, Func<A, TryOptionAsync<B>> f) where A : IDisposable =>
        ma.Bind(a =>
        {
            try
            {
                return f(a);
            }
            finally
            {
                a?.Dispose();
            }
        });
}

You should then be able to do:

public static TryOptionAsync<int> MaxUserAge() =>
    use(TryOptionAsync(new DbContext()), ctx => ctx.Users.Select(u => u.Age).MaxAsync());

Or,

public static TryOptionAsync<int> MaxUserAge() =>
    TryOptionAsync(new DbContext()).Use(ctx => ctx.Users.Select(u => u.Age).MaxAsync());

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.

0reactions
gwinteringcommented, Jun 3, 2019

@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:

  1. A function (thunk) that will run later
  2. A Task that will complete later

Starting with this:

    public static TryAsync<B> use<A, B>(TryAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        ma.Bind(a =>
        {
            try
            {
              return TryAsync(f(a));
            }
            finally
            {
                a?.Dispose();
            }
        });

let’s first remove a bit a sugar for clarity

    public static TryAsync<B> use<A, B>(TryAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        ma.Bind(a =>
        {
            try
            {
              return TryAsync(() => f(a));
            }
            finally
            {
                a?.Dispose();
            }
        });

Now it’s clear that we’ve closed over a in our thunk. This reference to a is problematic because a is disposed immediately after the thunk is returned, which means is not longer available when the thunk is actually evaluated (via Match or IfFailThrow, etc).

Here’s and idea, let’s move a out of our thunk to prevent that closure

    public static TryAsync<B> use<A, B>(TryAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        ma.Bind(a =>
        {
            try
            {
              var taskB = f(a)
              return TryAsync(() => taskB);
            }
            finally
            {
                a?.Dispose();
            }
        });

and instead close over taskB. This change moves us into problem 2 because taskB might still be working when we return, which means we might dispose a before taskB is done with it.

So let’s make sure we await taskB before we return:

    public static TryAsync<B> use<A, B>(TryAsync<A> ma, Func<A, Task<B>> f) where A : IDisposable =>
        ma.Bind(a => TryAsync(async () => 
        {
            try
            {
              var taskB = f(a)
              return await taskB;
            }
            finally
            {
                a?.Dispose();
            }
        }));

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 or try/finally) inside the TryAsyc factory call rather than call the TryAsync factory from inside using or try/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 the awaiting. Stay tuned to #567 for a better story around deferred structures and resource disposal.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Feature: Resource Monad · Issue #567 · louthy/language-ext
This is a proposal to provide more safety around IDisposable resources by putting them in the library's monadic framework.
Read more >
Implementing both IDisposable and IAsyncDisposable
The Disposable pattern exists because the derived class may want to add cleanup logic that can be either sync or async. You can't...
Read more >
Implement a DisposeAsync method
The IAsyncDisposable instance is cast as IDisposable, and if it's also not null , it's disposed of as well. Both instances are then...
Read more >
LanguageExt.Core\Monads\Alternative Value Monads\Try\Try
method TryAsync<Unit> ToAsync (this Try<Task> self) Source # ... Default value to use if the Try computation fails. returns ... where T :...
Read more >
C# (CSharp) LanguageExt Eff примеры использования
public static TryOptionAsync <Eff <B> > Traverse <A, ... Func <H, Eff <R> > Use) where H : IDisposable => EffMaybe(() => {...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found