Async, await in C#

Viewed 169

On closing of my application I have to do some clean up activity and I have written something like this in ClassA.cs

protected override void OnExit(ExitEventArgs e)
{
    Cleanup();
    base.OnExit(e);
}

private async Task Cleanup()
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (databaseInitialisation != null)
    {
        await databaseInitialisation.InitialiseDatabase();
    }
     databaseFile?.Dispose();
}

I have one more class ClassB.cs

public class DatabaseInitialisation : IDatabaseInitialisation
{
    private readonly string filterOptimisationPath;
    private readonly Task databaseInitialisation;

    private IDbConnectionFactory ConnectionFactory { get; }
    public async Task InitialiseDatabase() => await databaseInitialisation;

    public DatabaseInitialisation(string databaseFilePath, string filterOptimisationPath)
    {
        this.filterOptimisationPath = filterOptimisationPath;
        ConnectionFactory = new SqLiteConnectionFactory(databaseFilePath);
        databaseInitialisation = Task.Run(() => CreateDatabase(databaseFilePath));
    }
}

so now when I close my application, sometimes when cleanup is called execution of the program going from await is going to onexit method without executing the dispose method

1 Answers

The root of your trouble is that you need to call an async method from a sync method, and you cannot make that method async because it's part of the GUI framework. The general advice here would be to make your calling method async, but since you can't, there are a few ways you can accomplish this:

Option 1 -- Task.Run

Change your OnExit method to launch Cleanup on a thread pool thread and then synchronously wait for that using GetAwaiter().GetResult() to prevent deadlocks. Since this is only running once at shutdown, it should be fine, but launching background threads like this would not be recommended if this were a method that will run often as it could potentially lead to thread pool starvation.

protected override void OnExit(ExitEventArgs e)
{
    Task.Run(async () => await Cleanup().ConfigureAwait(false))
        .GetAwaiter().GetResult();
    base.OnExit(e);
}

Option 2 -- async void and a ManualResetEvent

This approach avoids using .GetAwaiter().GetResult() and launching on a thread pool thread, but any exception thrown in cleanup will cause the application to crash.

protected override void OnExit(ExitEventArgs e)
{
    using var signal = new ManualResetEventSlim(false);
    Cleanup(signal);
    signal.Wait();
    base.OnExit(e);
}

private async void Cleanup(ManualResetEventSlim signal)
{
    if (databaseInitialisation != null)
    {
        await databaseInitialisation.InitialiseDatabase();
    }
     databaseFile?.Dispose();
    signal.Set();
}

You could also use one of the Wait overloads to include a timeout.

As a side note, in a GUI application like this, you should really be calling .ConfigureAWait(false) on every task you await that isn't being called directly from a GUI event. Not doing so risks deadlocks.

One caveat to both these solutions is that if your Cleanup method or anything in its call stack, both of these methods will deadlock, and in that case, another solution is needed.

Related