Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a more readable alternative to calling ConfigureAwait(false) inside an async method?

I'm currently writing a lot of async library code, and I'm aware of the practice of adding ConfigureAwait(false) after each async call so as to avoid marshalling the continuation code back to the original (usually UI) thread context. As I don't like the unlabelled Boolean parameter, I tended to write this as ConfigureAwait(continueOnCapturedContext: false) instead.

I added an extension method to make it a little more readable (and reduce the typing somewhat):

public static class TaskExtensions
{
    public static ConfiguredTaskAwaitable<TResult> WithoutCapturingContext<TResult>(this Task<TResult> task)
    {
        return task.ConfigureAwait(continueOnCapturedContext: false);
    }

    public static ConfiguredTaskAwaitable WithoutCapturingContext(this Task task)
    {
        return task.ConfigureAwait(continueOnCapturedContext: false);
    }
}

So now I can have something like await SomethingAsync().WithoutCapturingContext() instead of await SomethingAsync().ConfigureAwait(continueOnCapturedContext: false). I consider it an improvement, however even this starts to grate when I have to call a number of async methods in the same block of code, as I end up with something similar to this:

await FooAsync().WithoutCapturingContext();
var bar = await BarAsync().WithoutCapturingContext();
await MoreFooAsync().WithoutCapturingContext();
var moreBar = await MoreBarAsync().WithoutCapturingContext();
// etc, etc

In my opinion it's starting to make the code a lot less readable.

My question was basically this: is there a way of reducing this down further (other than shortening the name of the extension method)?

like image 840
Steven Rands Avatar asked Nov 24 '14 16:11

Steven Rands


1 Answers

There is no global setting to prevent the tasks within the method from capturing the synchronization context, but what you can do is change the synchronization context for just the scope of that method. In your specific case you could change the context to the default sync context for just the scope of that method.

It's easy enough to write a simple disposable class that changes the sync context and then changes it back when disposed:

public class SyncrhonizationContextChange : IDisposable
{
    private SynchronizationContext previous;
    public SyncrhonizationContextChange(SynchronizationContext newContext = null)
    {
        previous = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(newContext);
    }

    public void Dispose()
    {
        SynchronizationContext.SetSynchronizationContext(previous);
    }
}

allowing you to write:

using(var change = new SyncrhonizationContextChange())
{
    await FooAsync();
    var bar = await BarAsync();
    await MoreFooAsync();
    var moreBar = await MoreBarAsync();
}

(Note setting the context to null means it'll use the default context.)

like image 93
Servy Avatar answered Sep 22 '22 18:09

Servy