【问题标题】:await on UserManager causes the error A second operation started on this context before a previous operation completed在 UserManager 上等待导致错误在前一个操作完成之前在此上下文上启动了第二个操作
【发布时间】:2017-08-17 13:32:38
【问题描述】:

背景

我正在开发一个 .Net Core API 来驱动歌词应用程序。用户可以注册、提交艺术家和歌词,并在此过程中获得荣誉/XP 积分。基本上是一个社区驱动的歌词网站。

代码

这是我的 ArtistController 类:

[Route("api/artists")]
public class ArtistsController : Controller
{
  private readonly IPermissionsService _permissionsService;
  private readonly IArtistsService _artistsService;

  public ArtistsController(IArtistsService artistsService, IPermissionsService permissionsService)
  {
    _permissionsService = permissionsService ?? throw new ArgumentNullException(nameof(permissionsService));
    _artistsService = artistsService ?? throw new ArgumentNullException(nameof(artistsService));
  }

  [HttpGet("{slug}")]
  [HttpGet("{slug}/lyrics", Name = "GetArtist")]
  public async Task<IActionResult> GetArtist(string slug)
  {
    if (!_artistsService.ArtistExists(slug)) return NotFound();
    var permissions = await _permissionsService.GetPermissions(HttpContext);
    var artist = _artistsService.GetArtistBySlug(slug, permissions.UserId, permissions.IsAdministrator);
    if (artist == null) return NotFound();
    return Ok(artist);
  }

  // other methods omitted
}

本着可测试性的精神,我创建了一个IPermissionsService,这样当我对控制器进行单元测试时,我可以很容易地做到这一点,而不必担心HttpContextUser

这是PermissionsService类的代码:

public class PermissionsService : IPermissionsService
{
  private string _userId;
  private bool _isAdministrator;
  private HttpContext _httpContext;
  private readonly UserManager<BbUser> _userManager;

  public PermissionsService(UserManager<BbUser> userManager)
  {
    _userManager = userManager;
  }

  public async Task<Permissions> GetPermissions(HttpContext httpContext)
  {
    _httpContext = httpContext;
    PopulateUserIdAndIsAdminFlag();
    var permissions = new Permissions
    {
      UserId = _userId,
      IsAdministrator = _isAdministrator
    };

    return await Task.Run(() => permissions);
  }

  private async void PopulateUserIdAndIsAdminFlag()
  {
    if (!IsAuthenticated()) return;
    var username = _httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
    var user = await _userManager.FindByNameAsync(username);
    var roles = await _userManager.GetRolesAsync(user);
    _userId = user.Id;
    _isAdministrator = roles.Contains("Admin");
  }

  private bool IsAuthenticated()
  {
    return _httpContext.User.Identity.IsAuthenticated;
  }
}

问题

当我运行 API 并尝试调用该端点时。我收到以下错误:

在前一个操作完成之前,在此上下文中启动了第二个操作。不保证任何实例成员都是线程安全的。

消息很清楚,但我不知道如何克服这个错误。在将该逻辑移到 PermissionsService 之前,我没有收到错误消息,一切正常!

【问题讨论】:

  • 仅供参考:您可以使用 await Task.FromResult(permissions); 代替 await Task.Run(() =&gt; permissions);
  • 一个比另一个好吗?
  • Task.FromResult 立即创建一个已完成的Task 对象,因此它不需要创建新线程,所以是的,它更好(在这种情况下)。文档:msdn.microsoft.com/en-us/library/hh194922(v=vs.110).aspx

标签: c# asp.net-core entity-framework-core


【解决方案1】:

执行async 工作的方法应该返回Task 而不是void,除非它们是事件处理程序。这将允许等待生成的Task。因为没有等待PopulateUserIdAndIsAdminFlag,所以您正在跨线程同时调用同一个DbContext 实例。如果你遵循调用堆栈:

  1. 代码输入GetPermissions
  2. 您在PopulateUserIdAndIsAdminFlag 开始工作但不要等待它完成
  3. 代码立即从 GetPermissions 返回(PopulateUserIdAndIsAdminFlag 中的代码也在执行中
  4. 代码继续并在_artistsService 上调用方法

这可能导致 DbContext 被多个线程同时调用,从而导致您的异常。

修复代码,等待PopulateUserIdAndIsAdminFlag 的结果。

  • 更改代码以等待PopulateUserIdAndIsAdminFlag 方法,使其返回类型Task
  • 等待PopulateUserIdAndIsAdminFlag的结果
  • GetPermissions 末尾不再需要将结果包装在Task
  • 我还建议重命名它并添加后缀Async,因为这被认为是返回类型Task 的方法的正确命名约定。这将导致名为 GetPermissionsAsyncPopulateUserIdAndIsAdminFlagAsync 的方法

更改代码:

public async Task<Permissions> GetPermissions(HttpContext httpContext)
{
    _httpContext = httpContext;
    // await result
    await PopulateUserIdAndIsAdminFlag();
    var permissions = new Permissions
    {
      UserId = _userId,
      IsAdministrator = _isAdministrator
    };

    // wrapping the result in Task is no longer necessary 
    return permissions;
}

// change void to Task
private async Task PopulateUserIdAndIsAdminFlag()
{
    if (!IsAuthenticated()) return;
    var username = _httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
    var user = await _userManager.FindByNameAsync(username);
    var roles = await _userManager.GetRolesAsync(user);
    _userId = user.Id;
    _isAdministrator = roles.Contains("Admin");
}

【讨论】:

    猜你喜欢
    • 2020-06-05
    • 2014-12-08
    • 1970-01-01
    • 2019-12-17
    • 1970-01-01
    • 2022-01-01
    • 2020-01-22
    • 1970-01-01
    • 2018-11-07
    相关资源
    最近更新 更多