我应该在像存储库这样的地方使用 ConfigureAwait(false) 吗?



刚刚阅读了这篇关于ConfigureAwait的文章,它让我思考了一个我已经有一段时间无法和平解决的问题。

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

你觉得怎么样?这是必要的还是我离得很远?还是运行时真的会为我处理这个问题?

没有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,即使代码(在我看来(比没有代码要糟糕得多。

在此处查看微软最佳实践

通过使用 ConfigureAwait,您可以启用少量并行性:某些异步代码可以与 GUI 线程并行运行,而不是不断地用一些工作来纠缠它。

除了性能之外,ConfigureAwait还有另一个重要方面:它可以避免死锁。

当您无法使用它时要小心:

当在需要上下文的方法中有 await 之后的代码时,不应使用 ConfigureAwait。对于 GUI 应用程序,这包括操作 GUI 元素、写入数据绑定属性或依赖于特定于 GUI 的类型(如调度程序/核心调度程序(的任何代码。

对于 ASP.NET 应用,这包括使用 HttpContext.Current 或生成 ASP.NET 响应的任何代码。

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

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

图书馆

建议在库中使用它,因为如果库的使用者决定同步等待异步方法,则可能会导致死锁。

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

排比

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

Stephen Cleary 在他关于 Core SynchronizationContext 的文章 ASP.NET 讨论了这个问题(请参阅"当心隐式并行"标题(。 ASP.NET Core 没有同步上下文,因此ConfigureAwait(false)在那里没有影响,但它也适用于使用ConfigureAwait(false)时通常具有上下文的任何应用。他在那里给出了示例代码,其中非线程安全代码最终可以并行运行。

结论

除非您有意识的理由,否则不要使用它。

最新更新