2017-08-17 1 views
1

HINTERGRUND abgeschlossenwarten auf Usermanager die Fehler verursachen Eine zweite Operation vor einer vorherigen Operation auf diesem Zusammenhang begann

Ich arbeite an einem Net Core-API einen Text Anwendung zu fahren. Nutzer können sich anmelden, Künstler & Songtexte einreichen und dabei Kudos/XP-Punkte sammeln. Im Grunde eine Community-basierte Songtextseite.

CODE

Hier meine ArtistController Klasse ist:

[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 
} 

Im Geiste der Testbarkeit, habe ich eine IPermissionsService, auf diese Weise, wenn ich Einheit Test kommen, um den Controller, kann ich leicht tun, so ohne sich Gedanken über HttpContext und User zu machen. Hier

ist der Code für PermissionsService Klasse:

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

PROBLEM

Wenn ich die API laufe und versuchen, diesen Endpunkt zu nennen. Ich bekomme den folgenden Fehler:

Die Nachricht ist ziemlich klar, aber ich bin mir nicht sicher, wie Sie diesen Fehler zu überwinden. Bevor ich diese Logik an die PermissionsService weitergab, bekam ich den Fehler nicht und alles funktionierte gut!

+0

FYI: statt 'erwarten Task.Run (() => Berechtigungen);', können Sie 'erwarten Aufgabe. FromResult (Berechtigungen); ' –

+0

Ist einer besser als der andere? – Ciwan

+2

'Task.FromResult' erstellt sofort ein' Task'-Objekt, das abgeschlossen ist, also muss es keinen neuen Thread erstellen, also ja, es ist besser (in diesem Fall). Doc: https://msdn.microsoft.com/en-us/library/hh194922(v=vs.110).aspx –

Antwort

2

Methoden, die async funktionieren, sollten Task und nicht void zurückgeben, außer sie sind Event-Handler. Dies wird es ermöglichen, dass der resultierende Task erwartet wird. Da PopulateUserIdAndIsAdminFlag nicht erwartet wird, führen Sie simultan Threads mit derselben DbContext Instanz aus. Wenn Sie den Call-Stack wie folgt vor:

  1. -Code tritt GetPermissions
  2. Sie beginnen Arbeit in PopulateUserIdAndIsAdminFlagaber warten Sie nicht für es abgeschlossen werden, um
  3. -Code sofort wieder aus GetPermissions (Code in PopulateUserIdAndIsAdminFlag wird auch nach wie vor der Ausführung)
  4. -Code weiter und ruft Methode auf _artistsService

Dies kann dazu führen, dass der DbContext gleichzeitig von mehreren Threads aufgerufen wird, was Ihre Ausnahme verursacht.

Fix den Code, so dass das Ergebnis von PopulateUserIdAndIsAdminFlag erwartet wird.

  • Code ändern PopulateUserIdAndIsAdminFlag Methode zu erwarten, so dass es Typ gibt Task
  • warten auf das Ergebnis der PopulateUserIdAndIsAdminFlag
  • Wrapping das Ergebnis in einem Task nicht mehr am Ende erforderlich ist GetPermissions
  • ich auch empfehlen, sie umzubenennen und das Suffix Async hinzuzufügen, da dies als richtige Namenskonvention für Methoden betrachtet wird, die den Typ Task zurückgeben. Das benannte in Methoden führen würde GetPermissionsAsync und PopulateUserIdAndIsAdminFlagAsync

Geänderte Code:

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"); 
} 
+0

Genie, danke. Arbeitete sofort! – Ciwan

Verwandte Themen