Search code examples
c#wpfasync-awaittaskconfigureawait

Should I use ConfigureAwait(false) in places like Repository?


Just read this article about ConfigureAwait and it made me think on an issue I haven't been able to come to peace with for some time now.

Consider the code below. Each dependency uses await to make the call asynchronous. My concern is that each time we exit an await it goes back to the UI thread and I don't want that until I'm actually at the top level where the UI needs to be updated in order to reduce thread context switching. That made me think that ConfigureAwait(false) should be used in layers below the UI to avoid unnecessary Post's (SynchronizationContext) to the UI thread.

What do you think? Is this necessary or am I way off? Or will the runtime actually take care of this for me?

Without ConfigureAwait(false):

public class ViewModel
{
    private readonly Service service;
    private readonly ICommand updateCommand;
    
    public ViewModel(Service service)
    {
        this.service = service;
        updateCommand = new RelayCommand(UpdateUser);
    }

    private async void UpdateUser()
    {
        Cursor.ShowWait();
        await service.UpdateUser(SelectedUser);
        Cursor.ShowDefault();
    }
}

public class Service
{
    private readonly Repository repository;
    
    public Service(Repository repository)
    {
        this.repository = repository;
    }
    
    public async Task UpdateUser(Model.User user)
    {
        var domainUser = Convert(user);
        await repository.UpdateUser(domainUser);
    }
}

public class Repository
{
    private readonly MyDbContext context;
    
    public Repository(MyDbContext context)
    {
        this.context = context;
    }
    
    public async Task UpdateUser(User user)
    {
        context.Users.Update(user);
        await context.SaveChangesAsync();
    }
}

public class MyDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
}   

With ConfigureAwait(false)

public class ViewModel
{
    private readonly Service service;
    private readonly ICommand updateCommand;
    
    public ViewModel(Service service)
    {
        this.service = service;
        updateCommand = new RelayCommand(UpdateUser);
    }

    private async void UpdateUser()
    {
        Cursor.ShowWait();
        await service.UpdateUser(SelectedUser);
        Cursor.ShowDefault();
    }
}

public class Service
{
    private readonly Repository repository;
    
    public Service(Repository repository)
    {
        this.repository = repository;
    }
    
    public async Task UpdateUser(Model.User user)
    {
        var domainUser = Convert(user);
        await repository.UpdateUser(domainUser).ConfigureAwait(false);
    }
}

public class Repository
{
    private readonly MyDbContext context;
    
    public Repository(MyDbContext context)
    {
        this.context = context;
    }
    
    public async Task UpdateUser(User user)
    {
        context.Users.Update(user);
        await context.SaveChangesAsync().ConfigureAwait(false);
    }
}

public class MyDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
}

Solution

  • You should always use configureAwait in libraries, even if the code (in my opinion) is much worse than without.

    Check microsoft best practices here

    By using ConfigureAwait, you enable a small amount of parallelism: Some asynchronous code can run in parallel with the GUI thread instead of constantly badgering it with bits of work to do.

    Aside from performance, ConfigureAwait has another important aspect: It can avoid deadlocks.

    Be careful of when you can't use it:

    You should not use ConfigureAwait when you have code after the await in the method that needs the context. For GUI apps, this includes any code that manipulates GUI elements, writes data-bound properties or depends on a GUI-specific type such as Dispatcher/CoreDispatcher.

    For ASP.NET apps, this includes any code that uses HttpContext.Current or builds an ASP.NET response

    In repositories, it's almost always the case that you can use it as you don't need to resume into the same Synchronization context.