2012-06-14 14 views
11

Me siento cómodo con la ejecución de trabajo sincrónico antes de llamar al SendAsync() del manejador interno y ejecutar el trabajo sincrónico después de que el manejador interno se haya completado a través de una finalización. por ejemplo:¿Cómo debería hacer DelegatingHandler una llamada asincrónica (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 }); 
} 

Sin embargo, ahora tienen que llamar a una operación obligado IO desde un controlador delegante. La operación de IO bound ya está envuelta como Task<bool>. Necesito usar el resultado para determinar si continúo en el controlador interno.

Un ejemplo sería hacer una llamada de red para autorizar una solicitud. Tengo que hacer esto para integrarme con un sistema existente. En general, creo que hay escenarios válidos para este problema, y ​​debería tener una solución viable.

¿Cuál es la forma correcta de implementar SendAsync en este caso, para que ejecute la tarea IO bound asincrónicamente y luego continúe ejecutando asincrónicamente el controlador interno?

El punto clave es que quiero estar seguro de que el hilo de solicitud no se deja bloqueado en ningún momento.

Respuesta

15

Bien, creo que tengo este crackeado. Estoy ilustrando esto con un escenario de autenticación: quiero autenticar a un usuario de forma asincrónica y usar el resultado para decidir si se devuelve un 401 o continuar con la cadena de manejo de mensajes.

El problema central es que no puede llamar al controlador interno SendAsync() hasta que obtenga el resultado de la autenticación asincrónica.

La clave para mí fue utilizar TaskCompletionSource (TCS) para controlar el flujo de ejecución. Esto me permitió devolver la Tarea desde el TCS y establecer un resultado siempre que quisiera, y lo más importante es retrasar la llamada a SendAsync() hasta que sepa que la necesito.

Así que configuré el TCS y luego comencé una tarea para hacer la autorización. En la continuación de esto, miro el resultado. Si está autorizado, invoco la cadena de controlador interno y adjunto una continuación a este (evitando cualquier bloqueo de hilo) que completa el TCS. Si la autenticación falla, simplemente complete el TCS allí y luego con un 401.

El resultado de esto es que ambas tareas asincrónicas se ejecutan a su vez sin ningún bloqueo de hilos. Cargué la prueba y parece funcionar bien.

Todo es mucho mejor en .NET 4.5 con la sintaxis async/await aunque ... aunque el enfoque con el TCS sigue siendo básicamente bajo las cubiertas, el código es mucho más simple.

¡Disfrútalo!

El primer fragmento se creó en .NET 4.0 con Web API Beta - el segundo en .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; 
} 

Aquí es una API de lanzamiento de la versión 4.5 de .NET Candidato/Web que es mucho más sexy con la nueva asíncrono/Await sintaxis:

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

¿cómo se implementa 'Authorize'? ¿Has instanciado un nuevo 'HttpClient'? – JobaDiniz

+0

La implementación de Authorize no es relevante aquí; es solo una función asíncrona que determina si la solicitud está permitida; cómo la implementa depende de usted.Uno podría, por ejemplo, hacer una verificación de la base de datos para ver si el usuario actual tiene acceso para hacer la solicitud actual en función de algunas reglas comerciales a medida. –

+0

Me preguntaba si realiza otra solicitud de HTTP, eso es todo ... en mi caso tengo que hacer otra llamada http, antes de la actual, y me han instanciado otro 'HttpClient'. Me preguntaba si podría reutilizar el actual dentro del controlador, pero parece que no puedo. – JobaDiniz

Cuestiones relacionadas