2

If an asynchronous call is made within a using statement, and the result of the call is processed asynchronously (i.e. the method within which this happens is async and returns before the result is loaded and processed), can the using statement go out of scope? In other words, is it safe to do something like this:

async void LoadAndProcessStuff()
{
    using(var ctx = CreateDBContext()){
        someResource.LoadStuffsAsync().ForEachAsync(stuff => ctx.Stuffs.Add(stuff));
    }
}

or

async void LoadAndProcessStuff2()
{
    using(var ctx = CreateDBContext()){
        ctx.Stuffs.Select(stuff => someResource.LoadMoreStuffsAsync(stuff))
            .ForEachAsync(stuff => ctx.Stuffs2.AddRange(stuff.Result));
    }
}

Or could ctx be Disposed by the time the ForEachAsync is called and cause an exception?

Liam
  • 27,717
  • 28
  • 128
  • 190
sammax
  • 31
  • 3
  • 3
    No this isn’t safe. `ctx` could, and surely will, be disposed while the asynchronous operation is running. This is basically the same as returning a disposable object from inside a using block. – InBetween Jan 06 '18 at 11:43
  • It would be fine if you `await`ed – Crowcoder Jan 06 '18 at 11:45
  • if I await, that means the method will only return when the whole operation is finished, right? I want it to run in the background because a whole bunch of such operations will be happening. And since the resource.LoadStuff is most likely a network operation, waiting until it is finished before starting the next operation would waste a lot of time – sammax Jan 06 '18 at 11:54
  • 2
    @no. Await will return immediately a task and mark the rest of the method as the continuation when the task is finished. What you need to do here is launch all the asynchronous operations *and then* `await` . Read [this answer](https://stackoverflow.com/a/48090684/767890) for an example. – InBetween Jan 06 '18 at 11:57
  • @InBetween That's definitely good to know :D Can I await in each of my LoadStuff methods and then await those methods from wherever they are called? I would like to keep them all seperated in their own methods because there is a bunch of logic in each method that I skipped for the examples. Also, would I have to await the ForEachAsync call, or await the LoadStuffsAsync or both? – sammax Jan 06 '18 at 12:02
  • Possible duplicate of [Synchronous implementation of interface that returns Task](https://stackoverflow.com/questions/28197902/synchronous-implementation-of-interface-that-returns-task) – Liam May 14 '18 at 16:01

1 Answers1

1

In an async method, the "using" keyword should be safe. The compiler just rewrites it as a "finally" expression, which works like all other exception handling in async methods. Note that Dispose() should NOT block or be long running, it should just release resources - unless you want to cripple your server.

This is an easy test case for the safe case:

using System;
using System.Threading.Tasks;

namespace so {
    sealed class Garbage : IDisposable {
        public void Dispose() {
            Console.WriteLine("Disposing Garbage");
        }
    }

    // Garbage is safely Disposed() only after delay is done
    class Program {
        public static async Task Main() {
            using (Garbage g = new Garbage()) {
                Console.WriteLine("Delay Starting");
                await Task.Delay(1000);
                Console.WriteLine("Delay Finished");
            }
        }
    }
}

However, if you have a non-async method that is returning a Task instead of awaiting it, it may be unsafe. That is because synchronous methods are only called once. There is no coroutine or state machine generated by compiler, so Dispose() HAS to be called right away - potentially while the Task is still running.

public Task DoWorkAsync()
{
    using (var service = new Service())
    {
        var arg1 = ComputeArg();
        var arg2 = ComputeArg();

        // NOT SAFE - service will be disposed
        // There is a high-probability that this idiom results 
        // in an ObjectDisposedException
        return service.AwaitableMethodAsync(arg1, arg2);
    }
}

For ref: http://www.thebillwagner.com/Blog/Item/2017-05-03-ThecuriouscaseofasyncawaitandIDisposable

KP99
  • 378
  • 1
  • 9