我应该在 Repository 等地方使用 ConfigureAwait(false) 吗?

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

刚刚阅读了有关 ConfigureAwait 的 this 文章,它让我思考了一个我已经有一段时间无法平静下来的问题。

考虑下面的代码。每个依赖项都使用 await 使调用异步。我担心的是,每次我们退出 await 时,它都会返回到 UI 线程,我不想这样,直到我真正处于 UI 需要的顶层被更新以减少线程上下文切换。这让我认为 ConfigureAwait(false) 应该在 UI 下面的层中使用,以避免不必要的 Post (SynchronizationContext) 到 UI 线程。

你怎么看?这是必要的还是我离开了?或者运行时真的会为我处理这个吗?

没有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; }
}   

使用 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; }
}

您应该始终在库中使用 configureAwait,即使代码(在我看来)比不使用要糟糕得多。

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.

不能使用时请注意:

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

在存储库中,几乎总是可以使用它,因为您不需要恢复到相同的同步上下文。

Athanasios 的回答很好。但我想我会补充这些观点。

图书馆

建议在库中使用,因为如果您的库的使用者决定同步等待您的异步方法,it can cause a deadlock

一个例外情况是,如果您提供同步方法 异步方法(即 DoSomething()DoSomethingAsync()),那么您不需要担心那个。如果您的库的使用者需要同步使用您的代码,他们可以使用同步方法。如果他们在您的异步方法上同步等待,那么,那是他们自己的问题。

平行度

关于 ConfigureAwait(false),我能给出的最好建议是 不要盲目地将它洒在任何地方。首先是因为在您不期望的情况下可能会丢失上下文,其次是隐含的并行性。 asyncawait 的全部好处是能够以与同步代码相同的方式 运行 异步代码。而且,在很大程度上,异步!=并行。但是,如果您使用 ConfigureAwait(false),您在 await 之后的代码可能会在您不期望的情况下并行 运行,并且您的代码可能不是线程安全的。

Stephen Cleary 在他关于 ASP.NET Core SynchronizationContext 的文章中对此进行了讨论(请参阅 "Beware Implicit Parallelism" 标题)。 ASP.NET Core 没有同步上下文,所以 ConfigureAwait(false) 在那里没有效果,但它适用于 通常在使用 ConfigureAwait(false) 时有上下文的任何应用程序也。他在那里给出了示例代码,其中 non-thread 安全代码可以并行结束 运行ning。

结论

除非有明确的理由,否则不要使用它。