2016-01-20 33 views
5

Po prostu dla uproszczonego szkicu przekonwertowałem wiele metod dostępu do danych na asynchroniczne, używając następującego wzorca, a zbyt proste wydaje się być wystarczająco dobre dla późniejszych iteracji. Jak bezpieczne jest to, czego brakuje i jak mam to robić?Czy wykonywanie długich połączeń jest tak proste?

Usługa, która zapewnia doskonałe prowadzenie rozmów:

private class UserService 
{ 
    public IdentityUser GetById(int id) 
    { 
     ... 
    } 
} 

private UserService _userService = new UserService(); 

Oryginalny sposób synchroniczny:

public IdentityUser GetById(int id) 
{ 
    return _userService.GetById(id); 
} 

Mój fantastyczny nowy sposób asynchroniczny:

public async Task<IdentityUser> GetByIdAsync(int id) 
{ 
    await Task.Run(() => _userService.GetById(id)); 
} 
+0

Długotrwałe zapytania mają długą listę możliwych problemów, z którymi trzeba się uporać. Dlaczego po prostu nie zainstalujesz SignalR nuget i nie ustawisz ich dla siebie - i wsparcia dla websocket! –

+0

@ MattiasÅslund: Różni ludzie mają różne definicje "długiego biegu". Mam wrażenie, że GetById() nie jest "długim bieganiem", który zasługuje na użycie SignalR. – StriplingWarrior

+0

Czy jest to coś, co zużywasz jako usługa WCF lub Web API? Jeśli tak, to tylko pogorszysz sytuację po stronie serwera. – Noseratio

Odpowiedz

5

Nie należy uczynić " fałszywe asynchroniczne "metody takie jak ta:

public async Task<IdentityUser> GetByIdAsync(int id) 
{ 
    await Task.Run(() => _userService.GetById(id)); 
} 

Powód, dla którego nazywam to "fałszywym asynchronizowaniem", jest taki, że nie ma nic nierozerwalnie asynchronicznego względem operacji. W takim przypadku powinieneś mieć tylko metodę synchroniczną. Jeśli osoba dzwoniąca chce to zrobić asynchronicznie, używając Task.Run, może to zrobić.

Kiedy coś jest z natury asynchroniczne? Po wysłaniu żądania do usługi WWW lub bazy danych, na przykład, między wysłaniem żądania a odebraniem odpowiedzi jest okres oczekiwania - żądanie jest operacją wewnętrznie asynchroniczną. Aby uniknąć blokowania wywołującego wątku, użyj async-await.

+0

Muszę zaimplementować metodę dokładnie tak, jak 'publiczne asynchroniczne zadanie GetByIdAsync (int id)', które nie będzie kompilowane, jeśli nie sprawię, że jego wewnętrzna operacja będzie fałszywa asynchronizuje się, a 'UserService' nie oferuje żadnych metod asynchronicznych. – ProfK

+2

@ProfK, a następnie zaimplementuj jako zwracające słowo kluczowe 'Task.FromResult' i bez' async' (które i tak nie jest częścią sygnatury metody kompilowanej). – Noseratio

+1

@Noseratio Wiem, że jak w 'Task.FromResult', i użyłem go w wielu miejscach, w których zmuszony byłem zwrócić' Task' lub nawet '' aa''. Myślę, że mógłbym zagrać to bezpiecznie i wykorzystać wszystkie moje "podróbki". – ProfK

2

Technicznie, że działa, ale działa poprzez utworzenie nowej nici do wykonywania synchronicznego operację, która sama jest zawijany i zablokowania z natury asynchronicznego pracy. Oznacza to, że nie uzyskujesz większych korzyści z przejścia na async w pierwszej kolejności.

Właściwa droga to przejście asynchroniczne do końca. Natomiast teraz prawdopodobnie masz coś takiego:

private class UserService 
{ 
    public IdentityUser GetById(int id) 
    { 
     return mContext.Users.Single(u => u.Id == id); 
    } 
} 

... należy teraz utworzyć wersji asynchronicznej:

private class UserService 
{ 
    public async Task<IdentityUser> GetByIdAsync(int id) 
    { 
     return await mContext.Users.SingleAsync(u => u.Id == id); 
    } 
} 

Zastosowanie:

public async Task<IdentityUser> GetByIdAsync(int id) 
{ 
    return await _userService.GetByIdAsync(id); 
} 

zakładając, oczywiście, że Twoja podstawowa struktura obsługuje asynchroniczne metody, takie jak SingleAsync() dla operacji z natury asynchronicznych, dzięki temu system może zwolnić bieżący wątek podczas oczekiwania na magistrale danych e operacja do ukończenia. Wątek można ponownie wykorzystać w innym miejscu, a po zakończeniu operacji można użyć dowolnej dostępnej nici w tym czasie.

Prawdopodobnie warto również przeczytać i przyjąć these Best Practices. Prawdopodobnie będziesz chciał użyć numeru .ConfigureAwait(false) w dowolnym miejscu, w którym nie masz dostępu do informacji kontekstowych, takich jak sesje i żądania.

Ta odpowiedź zakłada oczywiście, że GetById jest z natury asynchroniczny: pobierasz go z dysku twardego lub lokalizacji sieciowej lub coś podobnego. Jeśli oblicza się identyfikator użytkownika przy użyciu długotrwałej pracy procesora, to dobrym pomysłem jest przejście na Task.Run() i prawdopodobnie będziesz chciał dodatkowo określić, że jest to długotrwałe zadanie w argumentach dla Task.Run().

+0

Nie obliczam "id", szukam użytkownika w DB z pewnym identyfikatorem, a może DB ma miliony użytkowników i nie ma indeksu na "Id" – ProfK

+0

@ProfK: Od twojego back-endu bazy danych nie obsługuje zadań asynchronicznych, dlaczego starasz się, aby twoja metoda 'GetByIdAsync' async' była na pierwszym miejscu? Jeśli jest tak, że osoba dzwoniąca może przejść do innego zadania, podczas gdy ten uruchamia się, a następnie sprawić, aby osoba dzwoniąca była odpowiedzialna za wywoływanie 'Task.Run()'. (zobacz http://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html) Czy jesteś po prostu odporny na przyszłe zmiany w swoim kodzie, mając nadzieję, że pewnego dnia będzie to asynchroniczne? W takim przypadku 'return Task.FromResult (_userService.GetById (id));'. W przeciwnym razie nie ma powodu, aby w ogóle używać 'async', więc nie rób tego. – StriplingWarrior

0

Funkcja Task.Run() powinna być używana tylko do pracy związanej z procesorem. Nie do końca pamiętam dlaczego. Spróbuj utworzyć metodę GetByIdAsync(), która ostatecznie wywoła zasób asynchroniczny.

+0

Nie mam zasobu asynchronicznego, którego zasobem jest NHibernate, co, o ile się dowiedziałem, jeszcze nie obsługuje asynchronizacji. – ProfK