2013-02-27 7 views
6

Pracuję nad aplikacją sieci web ASP.NET MVC 4. Korzystam z .NET 4.5 i próbuję skorzystać z nowych asynchronicznych interfejsów API.Powrót przed asynchroniczne Zadanie zakończone

Mam kilka sytuacji, w których chcę zaplanować zadanie asynchroniczne, aby uruchomić je później, a ja natychmiast zwrócę natychmiast ważną wartość. Na przykład, tutaj jest „Logowanie” metoda, która chcę wrócić nowy SessionID tak szybko, jak to możliwe, ale po Wróciłem sessionid Chcę oczyścić stary wygasł SessionID użytkownika:

public async Task<Guid> LogIn(string UserName, string Password) 
{ 
    //Asynchronously get ClientID from DB using UserName and Password 

    Session NewSession = new Session() 
    { 
     ClientID = ClientID, 
     TimeStamp = DateTime.Now 
    }; 
    DB.Sessions.Add(NewSession); 
    await DB.SaveChangesAsync(); //NewSession.ID is autopopulated by DB 

    CleanSessions(ClientID); //Async method which I want to execute later 

    return NewSession.ID; 
} 

private async void CleanSessions(int ClientID) 
{ 
    //Asynchronously get expired sessions from DB based on ClientID and mark them for removal 
    await DB.SaveChangesAsync(); 
} 

ja już próbowałem wielu różnych rzeczy, w tym kombinacji Task.Run() i Parallel.Invoke(), ale CleanSessions nigdy nie zostanie wywołany. Jak mogę zaplanować zadania w tle?

+0

Na tej podstawie 'CleanSessions' powinien otrzymać * uruchomiony * po zakończeniu operacji" SaveChangesAsync ". Być może może to być spowodowane zadaniem 'Task' i' await' aby złapać błędy itp. - ale powinno działać "tak, jak jest" z tego, co widzę tam –

+0

Uwaga: Powinieneś * nigdy * używać 'async void' dla swoich metod, z wyjątkiem programów obsługi zdarzeń na najwyższym poziomie. –

+0

@MarcGravell: ASP.NET będzie czekał na zakończenie wszystkich metod 'async void' przed wysłaniem odpowiedzi. Op chce mieć ogień i zapomnieć. –

Odpowiedz

12

Uruchomienie zadań w środowisku ASP.NET bez żądania nie jest zalecane. It's dangerous.

Powiedział, że zmiany CleanSessions wrócić Task i można zrobić to tak:

Task.Run(() => CleanSessions()); 

W twoim przypadku, myślę, że byłoby OK, bo nie ma żadnego problemu długoterminowego jeśli CleanSessions robi” t wykonać lub zostanie zakończone w trakcie wykonywania (co może się zdarzyć w ASP.NET z powodu recyklingu). Jeśli chcesz powiadomić ASP.NET, że masz jakieś prace w toku, który nie jest związany z prośbą, można użyć BackgroundTaskManager from my blog takiego:

BackgroundTaskManager.Run(() => CleanSessions()); 
+1

Dokładnie tego szukałem, dziękuję. Właściwie to już wypróbowałem to, ale wystąpił błąd w mojej metodzie CleanSessions(), który powodował wyjątek, a ponieważ metoda jest wykonywana bez żądania, wyjątek nie został przechwycony. Sprawiło mi to wrażenie, że metoda nie została w ogóle wywołana! Uwaga dla każdego, kto chce skorzystać z tej sztuczki, przeczytaj linki w tej odpowiedzi i upewnij się, że naprawdę rozumiesz, dlaczego jest to "niebezpieczne". – user1084447

+0

Co powiesz na 4 lata później? Być może aktualizacja? Twój blog nie był aktualizowany od 2014 r., Nowe informacje? – CularBytes

+0

@CularBytes: Nie; ASP.NET nadal działa w ten sam sposób, a ta odpowiedź jest nadal całkowicie poprawna. 'BackgroundTaskManager' nie działa na ASP.NET Core, ale możesz zrobić to samo używając' IHostedService' lub używając 'IApplicationLifetime' zamiast' IRegisteredObject'. –

0

jeśli, jak już wspomniano, staramy się zrobić coś "podpalić i zapomnieć", ale wewnątrz ASP.NET - wtedy możesz spojrzeć na ConfigureAwait; możesz użyć tego jako części await, aby nakazać mu zignorowanie kontekstu synchronizacji. Jest to kontekst synchronizacji, który wiąże go z powrotem do potoku wykonawczego ASP.NET, ale jeśli to nie jest konieczne, można tego uniknąć. Na przykład:

public async Task<Guid> LogIn(string UserName, string Password) 
{ 
    //Asynchronously get ClientID from DB using UserName and Password 

    Session NewSession = new Session() 
    { 
     ClientID = ClientID, 
     TimeStamp = DateTime.Now 
    }; 
    DB.Sessions.Add(NewSession); 
    await DB.SaveChangesAsync().ConfigureAwait(false); //NewSession.ID is autopopulated by DB 

    await CleanSessions(ClientID).ConfigureAwait(false); //Async method which I want to execute later 

    return NewSession.ID; 
} 
// simplified: no need for this to be full "async" 
private Task CleanSessions(int ClientID) 
{ 
    //Asynchronously get expired sessions from DB based on ClientID 
    return DB.SaveChangesAsync(); 
} 
+1

To rozwiązanie ma najlepszą obsługę błędów (używając 'await' na' CleanSessions'), ale nie zwraca odpowiedzi "wcześnie"; odpowiedź zostanie wysłana po zakończeniu czynności "CleanSessions". –

+1

Ah, tak - nawet jeśli * ten kod * użył ConfigureAwait, odpowiedź wyraźnie nie może zostać wysłana, dopóki te "oczekiwania" nie zostaną zakończone. Więc jeśli wyobrazisz sobie kod * calling * oczekujący na to zadanie, będzie on w kontekście synchronizacji ASP.NET. –

+0

Tak, spróbowałem, wciąż czeka na CleanSessions(), zanim będzie kontynuowane. Nie osiąga tego, co próbuję zrobić. – user1084447