2012-06-14 10 views
11

Jestem wygodny w wykonywaniu pracy synchronicznej przed wywołaniem funkcji SendAsync obsługi wewnętrznej() i wykonywaniem pracy synchronicznej po ukończeniu wewnętrznego programu obsługi przez ukończenie. np.W jaki sposób funkcja DelegatingHandler powinna wykonywać wywołanie asynchroniczne (ASP.NET MVC Web API)?

protected override Task<HttpResponseMessage> SendAsync( 
    HttpRequestMessage request, CancellationToken cancellationToken) 
{ 
    // do some sync work before inner handler here 

    var sendTask = base.SendAsync(request, cancellationToken); 
    return sendTask.ContinueWith( 
     task => { // do some sync work afterwards here }); 
} 

Jednak teraz muszę wywołać operację związaną z IO z programu obsługi delegowania. Operacja związana z IO jest już opakowana jako Task<bool>. Potrzebuję użyć wyniku, aby określić, czy kontynuować do wewnętrznej obsługi.

Przykładem może być połączenie sieciowe w celu autoryzacji żądania. Muszę to zrobić, aby zintegrować się z istniejącym systemem. Ogólnie rzecz biorąc, uważam, że istnieją poprawne scenariusze tego problemu i powinien on mieć wykonalne rozwiązanie.

Jaki jest właściwy sposób wdrożenia SendAsync w tym przypadku, aby wykonać asynchronicznie zadanie IO bounded i kontynuować asynchroniczne wykonywanie wewnętrznego programu obsługi?

Najważniejsze jest to, że chcę mieć pewność, że wątek żądania nie zostanie zablokowany w żadnym momencie.

Odpowiedz

15

OK, myślę, że mam to popękane. Zilustruję to scenariuszem uwierzytelniania: chcę asynchronicznie uwierzytelnić użytkownika i użyć wyniku, aby zdecydować, czy zwrócić 401, czy kontynuować z łańcuchem obsługi komunikatów.

Głównym problemem jest to, że nie można wywoływać wewnętrznego programu obsługi SendAsync(), dopóki nie otrzymasz wyniku z asynchronicznego uwierzytelniania.

Kluczem do zrozumienia dla mnie było użycie TaskCompletionSource (TCS) do sterowania przepływem wykonania. Pozwoliło mi to zwrócić zadanie z TCS i ustawić wynik na nim, kiedy tylko mi się podoba - i co najważniejsze, aby opóźnić wywołanie SendAsync(), dopóki nie będę wiedział, że go potrzebuję.

Tak więc skonfigurowałem TCS, a następnie uruchomiłem zadanie autoryzacji. Kontynuując to, patrzę na wynik. Jeśli jest to autoryzowane, przywołuję wewnętrzny łańcuch obsługi i dołączam kontynuację do tego (unikając jakiegokolwiek blokowania gwintów), która uzupełnia TCS. Jeśli uwierzytelnienie się nie powiedzie, wystarczy wypełnić TCS tam, a następnie z 401.

Wynikiem tego jest, że oba asynchroniczne zadania są wykonywane po kolei bez blokowania wątków. Wczytuję to i wygląda na to, że działa dobrze.

To wszystko jest o wiele przyjemniejsze w .NET 4.5 ze składnią async/await ... chociaż podejście z TCS wciąż odbywa się w zasadzie pod kodem, kod jest znacznie prostszy.

Ciesz się!

Pierwszy fragment został zbudowany na platformie .NET 4.0 z interfejsem API WWW Beta - drugi na platformie .NET 4.5/Web API RC.

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken) 
{ 
    var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>(); 

    // Authorize() returns a started 
    // task that authenticates the user 
    // if the result is false we should 
    // return a 401 immediately 
    // otherwise we can invoke the inner handler 
    Task<bool> authenticationTask = Authorize(request); 

    // attach a continuation... 
    authenticationTask.ContinueWith(_ => 
    { 
     if (authenticationTask.Result) 
     { 
      // authentication succeeded 
      // so start the inner handler chain 
      // and write the result to the 
      // task completion source when done 
      base.SendAsync(request, cancellationToken) 
       .ContinueWith(t => taskCompletionSource.SetResult(t.Result)); 
     } 
     else 
     { 
      // authentication failed 
      // so complete the TCS immediately 
      taskCompletionSource.SetResult(
       new HttpResponseMessage(HttpStatusCode.Unauthorized)); 
     } 
    }); 

    return taskCompletionSource.Task; 
} 

Oto .NET 4.5/Web API Release Candidate w wersji, która jest dużo bardziej seksownego z nowym asynchroniczny/Oczekujcie składnia:

protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken) 
{ 
    // Authorize still has a Task<bool> return type 
    // but await allows this nicer inline syntax 
    var authorized = await Authorize(request); 

    if (!authorized) 
    { 
     return new HttpResponseMessage(HttpStatusCode.Unauthorized) 
     { 
      Content = new StringContent("Unauthorized.") 
     }; 
    } 

    return await base.SendAsync(request, cancellationToken); 
} 
+0

skąd realizowane 'Authorize'? Czy wprowadziłeś nowy 'HttpClient'? – JobaDiniz

+0

Implementacja Authorize nie ma tu zastosowania - jest to tylko funkcja asynchroniczna, która określa, czy żądanie jest dozwolone - sposób wdrożenia go zależy od Ciebie.Można na przykład dokonać sprawdzenia bazy danych, aby sprawdzić, czy bieżący użytkownik ma dostęp do bieżącego żądania w oparciu o pewne niestandardowe reguły biznesowe. –

+0

Po prostu zastanawiałem się, czy możesz utworzyć kolejną prośbę o http, to wszystko ... w moim scenariuszu potrzebuję wykonać inne wywołanie http, przed bieżącym, a ja jestem zaindeksowany innym 'HttpClient'. Zastanawiam się, czy mogę ponownie użyć bieżącego w programie obsługi, ale wygląda na to, że nie mogę – JobaDiniz

Powiązane problemy