2015-02-10 27 views
23

pracuję z działaniami asynchronicznych i użyć HttpContext.Current.User jak tenPrawidłowe sposobem użycia HttpContext.Current.User z async czekają

public class UserService : IUserService 
{ 
    public ILocPrincipal Current 
    { 
     get { return HttpContext.Current.User as ILocPrincipal; } 
    } 
} 

public class ChannelService : IDisposable 
{ 
    // In the service layer 
    public ChannelService() 
      : this(new Entities.LocDbContext(), new UserService()) 
     { 
     } 

    public ChannelService(Entities.LocDbContext locDbContext, IUserService userService) 
    { 
     this.LocDbContext = locDbContext; 
     this.UserService = userService; 
    } 

    public async Task<ViewModels.DisplayChannel> FindOrDefaultAsync(long id) 
    { 
    var currentMemberId = this.UserService.Current.Id; 
    // do some async EF request … 
    } 
} 

// In the controller 
[Authorize] 
[RoutePrefix("channel")] 
public class ChannelController : BaseController 
{ 
    public ChannelController() 
     : this(new ChannelService() 
    { 
    } 

    public ChannelController(ChannelService channelService) 
    { 
     this.ChannelService = channelService; 
    } 

    // … 

    [HttpGet, Route("~/api/channels/{id}/messages")] 
    public async Task<ActionResult> GetMessages(long id) 
    { 
     var channel = await this.ChannelService 
      .FindOrDefaultAsync(id); 

     return PartialView("_Messages", channel); 
    } 

    // … 
} 

Mam kod niedawno refactored, wcześniej miałem dać użytkownik każdego połączenia z usługą. Teraz czytam ten artykuł http://trycatchfail.com/blog/post/Using-HttpContext-Safely-After-Async-in-ASPNET-MVC-Applications.aspx i nie jestem pewien, czy mój kod nadal działa. Czy ktoś ma lepsze podejście do obsługi tego? Nie chcę przekazywać użytkownikowi każdego żądania do usługi.

Odpowiedz

37

Dopóki twój web.config settings are correct, async/await działa doskonale z HttpContext.Current. Zalecam ustawienie httpRuntimetargetFramework do 4.5, aby usunąć wszystkie zachowania "tryb dziwactwa".

Po wykonaniu tej czynności zwykły async/await będzie działać doskonale. Wystąpią problemy, jeśli pracujesz nad innym wątkiem lub jeśli Twój kod await jest niepoprawny.


Po pierwsze, problem z innym wątkiem; jest to drugi problem z wpisu na blogu, z którym się łączysz. Kod taki jak ten oczywiście nie będzie działał poprawnie:

async Task FakeAsyncMethod() 
{ 
    await Task.Run(() => 
    { 
    var user = _userService.Current; 
    ... 
    }); 
} 

Problem ten nie ma nic wspólnego z kodem asynchronicznym; ma to związek z pobieraniem zmiennej kontekstowej z wątku puli wątków (niezwiązanego z żądaniem). Dokładnie ten sam problem wystąpi, jeśli spróbujesz zrobić to synchronicznie.

Podstawowy problem polega na tym, że asynchroniczna wersja korzysta z asynchronii fałszywej. To niewłaściwe, zwłaszcza na ASP.NET.Rozwiązaniem jest po prostu usunąć fałszywy-asynchroniczny kod i uczynić go synchroniczny (lub naprawdę asynchroniczny, jeśli rzeczywiście ma prawdziwy asynchroniczny do zrobienia):

void Method() 
{ 
    var user = _userService.Current; 
    ... 
} 

Technika zalecana w połączonej blogu (zawijania do HttpContext i dostarczenie go do wątku roboczego) jest wyjątkowo niebezpieczny. HttpContext jest przeznaczony do uzyskiwania dostępu tylko z jednego wątku na raz, a AFAIK nie jest w ogóle bezpieczny dla wątków. Więc dzielenie się nim w różnych wątkach wymaga świata cierpienia.


Jeśli kod await jest nieprawidłowy, powoduje to podobny problem. ConfigureAwait(false) to technika powszechnie używana w kodzie biblioteki do powiadamiania środowiska wykonawczego o tym, że nie musi ona powracać do określonego kontekstu. Rozważ ten kod:

async Task MyMethodAsync() 
{ 
    await Task.Delay(1000).ConfigureAwait(false); 
    var context = HttpContext.Current; 
    // Note: "context" is not correct here. 
    // It could be null; it could be the correct context; 
    // it could be a context for a different request. 
} 

W tym przypadku problem jest oczywisty. ConfigureAwait(false) informuje program ASP.NET, że reszta obecnej metody nie potrzebuje kontekstu, a następnie natychmiast uzyskuje dostęp do tego kontekstu. Po uruchomieniu za pomocą wartości kontekstowych w swoim implementacji interfejsu, choć problem nie jest tak oczywiste:

async Task MyMethodAsync() 
{ 
    await Task.Delay(1000).ConfigureAwait(false); 
    var user = _userService.Current; 
} 

Kod ten jest tak źle, ale nie jest tak oczywisty sposób błędne, ponieważ kontekst jest ukryty za interfejsu.

więc ogólna zasada mówi: użycie ConfigureAwait(false) jeśli wiesz, że metoda nie zależy od kontekstu (bezpośrednio lub pośrednio); w przeciwnym razie nie używaj ConfigureAwait. Jeśli jest to dopuszczalne w projekcie mają implementacje interfejsu wykorzystać kontekst w ich realizacji, a następnie każda metoda, która wywołuje metodę interfejs powinien nie użycie ConfigureAwait(false):

async Task MyMethodAsync() 
{ 
    await Task.Delay(1000); 
    var user = _userService.Current; // works fine 
} 

Dopóki podążać tą wytyczną, async/await będzie doskonale działać z HttpContext.Current.

+1

Jeśli chcesz wiedzieć, w którym środowisku wykonawczym działa twoja aplikacja ASP.NET, uderz w punkt przerwania i użyj właściwości statycznej: 'HttpRuntime.TargetFramework'. –

2

Asynchronizacja jest w porządku. Problem polega na tym, że publikujesz pracę do innego wątku. Jeśli twoja aplikacja jest skonfigurowana jako 4.5+, asynchroniczne wywołanie zwrotne zostanie opublikowane w oryginalnym kontekście, więc będziesz mieć również odpowiednie HttpContext itp.

Tak czy inaczej nie chcesz mieć dostępu do stanu dzielonego w innym wątku, i z Task s, rzadko musisz zająć się tym jawnie - po prostu upewnij się, że wstawiłeś wszystkie swoje dane wejściowe jako argumenty i tylko zwracasz odpowiedź, zamiast czytać lub pisać do stanu wspólnego (np. HttpContext, pola statyczne itp.)

2

Nie ma problemu, jeśli Twój ViewModels.DisplayChannel jest prostym obiektem bez dodatkowej logiki.

Może wystąpić problem, jeśli wynik twoich odniesień Task do "niektórych obiektów kontekstu", f.e. do HttpContext.Current. Takie obiekty są często dołączane do wątku, ale cały kod po await może być wykonany w innym wątku.

Należy pamiętać, że UseTaskFriendlySynchronizationContext nie rozwiązuje wszystkich problemów. Jeśli mówimy o ASP.NET MVC, to ustawienie zapewnia, że ​​Controller.HttpContext zawiera poprawną wartość jak przed await jak po. Nie gwarantuje jednak, że HttpContext.Current zawiera poprawną wartość, a po await nadal może być null.

+0

Jeśli dobrze rozumiem twoją odpowiedź. Czy istnieje problem, jeśli mam po asynchronicznym żądaniu EF inne wywołania serwisowe (np. Do innej usługi), które używają własnej usługi UserService – Magu

+0

Tak, jest. Spróbuj zwrócić proste obiekty (i uniknąć leniwego ładowania elementów EF). –