4

I have the following function:

private async Task DoSomething(NamespaceConnectionInfo nci)
{
    var session = await m_sessionProvider.GetSessionAsync(nci);
    SomeLegacySynchronousCode(session);
    await m_sessionProvider.EndSessionAsync(session);
}

where EndSessionAsync logs and swallows any exception (like a good destructor).

The problem is that SomeLegacySynchronousCode may throw an exception and then the session leaks.

It is clear to me perfectly why the following code is illegal:

private async Task DoSomething(NamespaceConnectionInfo nci)
{
    var session = await m_sessionProvider.GetSessionAsync(nci);
    try
    {
        SomeLegacySynchronousCode(session);
    }
    finally
    {
        await m_sessionProvider.EndSessionAsync(session);
    }
}

So, I am looking for an alternative that would be both correct and elegant.

Variant I

private async Task DoSomething(NamespaceConnectionInfo nci)
{
    var session = await m_sessionProvider.GetSessionAsync(nci);
    Exception exc = null;
    try
    {
        SomeLegacySynchronousCode(session);
    }
    catch (Exception e)
    {
        exc = e;
    }
    await m_sessionProvider.EndSessionAsync(session);
    if (exc != null)
    {
        // Wrap to preserve the original stack trace.
        throw new AggregateException(exc);
    }
}

Variant II

private Task DoSomething(NamespaceConnectionInfo nci)
{
    return m_sessionProvider.GetSessionAsync(nci).ContinueWith(t =>
    {
        Task result = null;
        try
        {
            SomeLegacySynchronousCode(t.Result);
        }
        finally
        {
            if (t.Exception == null)
            {
                result = m_sessionProvider.EndSessionAsync(t.Result);
            }
        }
        return result;
    }).Unwrap();
}

Neither are as elegant as the aforementioned illegal async/await version.

I am looking to improve over the two variants that I have proposed, because both are ugly, frankly.

Any ideas?

mark
  • 59,016
  • 79
  • 296
  • 580

1 Answers1

4

The commonly-accepted answer appears to be similar to your Variation 1:

You can move the logic outside of the catch block and rethrow the exception after, if needed, by using ExceptionDispatchInfo.

static async Task f()
{
    ExceptionDispatchInfo capturedException = null;
    try
    {
        await TaskThatFails();
    }
    catch (MyException ex)
    {
        capturedException = ExceptionDispatchInfo.Capture(ex);
    }

    if (capturedException != null)
    {
        await ExceptionHandler();

        capturedException.Throw();
    }
}

This way, when the caller inspects the exception's StackTrace property, it still records where inside TaskThatFails it was thrown.

Community
  • 1
  • 1
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Missed that question. What is `ExceptionHandler` ? The ones I can see in MSDN are in `System.ServiceModel.Dispatcher` and in `System.Reflection.Emit` namespaces. None seem to be relevant. – mark Mar 13 '14 at 21:48
  • `ExceptionHandler()` is just a placeholder for your exception-handling logic. In your case, it would be `m_sessionProvider.EndSessionAsync(session);` – StriplingWarrior Mar 14 '14 at 00:50