2012-05-08 20 views
5

Aparentemente, no entiendo cómo usar el método ContinueWith. Mi objetivo es ejecutar una tarea y, cuando la complete, devolver un mensaje.Tarea.Continuar Con la orden de ejecución

Aquí está mi código:

public string UploadFile() 
    { 
     if (Request.Content.IsMimeMultipartContent()) 
     { 
      //Save file 
      MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files")); 
      Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider); 

      string filename = "Not set"; 

      task.ContinueWith(o => 
      { 
       //File name 
       filename = provider.BodyPartFileNames.First().Value; 
      }, TaskScheduler.FromCurrentSynchronizationContext()); 

      return filename; 
     } 
     else 
     { 
      return "Invalid."; 
     } 
    } 

El "nombre de archivo" variables siempre devuelve "No establecido". Parece que el código dentro del método ContinueWith nunca se llama. (Se llama si depuro a través de línea por línea en VS.)

Se está llamando a este método en mi controlador ASP.NET Web API/AJAx POST.

¿Qué estoy haciendo mal aquí?

+2

es porque está haciendo una operación asíncrona. –

+0

también, aparte de que las tareas son asincrónicas, creo que ni siquiera se han iniciado. – GolfWolf

Respuesta

7

Si está utilizando una operación asincrónica, el mejor enfoque sería hacer que su operación sea asíncrona también, de lo contrario perderá las ventajas de la llamada asincrónica que está realizando. Intente reescribir su método de la siguiente manera:

public Task<string> UploadFile() 
{ 
    if (Request.Content.IsMimeMultipartContent()) 
    { 
     //Save file 
     MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files")); 
     Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider); 

     return task.ContinueWith<string>(contents => 
     { 
      return provider.BodyPartFileNames.First().Value; 
     }, TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
    else 
    { 
     // For returning non-async stuff, use a TaskCompletionSource to avoid thread switches 
     TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(); 
     tcs.SetResult("Invalid."); 
     return tcs.Task; 
    } 
} 
+0

¡Guau, gracias! Esto funcionó para mí. Gracias a todos los demás también: agradezco toda su ayuda. – Rivka

+0

Tengo una pregunta sobre el TaskScheduler.FromCurrentSynchronizationContext - mi comprensión es que realmente es un método de interfaz de usuario (sincronización con el hilo de la interfaz de usuario), pero me pregunto si hay algún motivo que pueda usarse dentro de un método webapi. ¿Hay alguno? – AlexGad

+0

El contexto de sincronización también se puede usar para almacenar algunas variables locales de subprocesos y asegurarse de que se vuelvan a llenar cuando el control regrese a la continuación. Un ejemplo sería el 'Thread.CurrentPrincipal'. Si mal no recuerdo, el tiempo de ejecución de ASP.NET también define un contexto de sincronización para ese caso. – carlosfigueira

1

Debe devolver el tipo Task<T> del método, en este caso sería Task<string>.

+0

Intenté esto, pero tengo el mismo problema: el "nombre de archivo" no se está configurando. – Rivka

0

Está utilizando una operación de asincronización. Si desea esperar a su finalización, usted tiene que utilizar el método Wait lo contrario de su tarea:

task.ContinueWith(o => 
     { 
      //File name 
      filename = provider.BodyPartFileNames.First().Value; 
     ).Wait(); 

return filename; 

Editar: Algunos métodos Asynch comienzan la tarea tan pronto como se cree, mientras que otros se preguntan para iniciarlos explícitamente. Debe consultar la documentación de cada uno para estar seguro. En este caso, parece que la tarea se inicia automáticamente.

+0

Intenté esto. No parece devolver nada (ni siquiera "No establecido") usando Wait(). – Rivka

+0

Entonces significa que las tareas no se ejecutan cuando se crean. Edité mi respuesta en consecuencia. – Falanwe

+0

Obtener el error No se puede llamar al inicio en una tarea con acción nula. El estado de la tarea era "RanToCompletion". – Rivka

2

Las razones de la variable de no ser conjunto son:

  • las tareas se crean instancias, pero no se ejecutan.
  • incluso si se ejecutaban las tareas, la función probablemente regresaría antes de que terminaran de ejecutarse, por lo que aún devolvería "No establecida". La solución para esto es esperar a que finalice la tarea final (la configuración fileName).

Su código podría ser fijado de esta manera:

public string UploadFile() 
{ 
    if (Request.Content.IsMimeMultipartContent()) 
    { 
     //Save file 
     MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files")); 
     Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider); 

     string filename = "Not set"; 

     var finalTask = task.ContinueWith(o => 
      { 
       //File name 
       filename = provider.BodyPartFileNames.First().Value; 
      }, TaskScheduler.FromCurrentSynchronizationContext()); 

     task.Start(); 

     finalTask.Wait(); 

     return filename; 
    } 
    else 
    { 
     return "Invalid."; 
    } 
} 

Las adiciones son los siguientes:

  • asignado el valor de retorno de task.ContinueWith a una variable llamada finalTask. Necesitamos esta tarea, ya que habrá que esperar a que termine
  • comenzó la tarea (la línea task.Start();)
  • esperaron a la tarea final para terminar antes de regresar (finalTask.Wait();)

Si es posible, por favor considere no implementar esto de forma asíncrona, porque al final es sincrónico (está esperando que termine) y la implementación actual agrega complejidad que probablemente podría evitarse.

pensar en hacer algo en este sentido (si es posible):

public string UploadFile() 
{ 
    if (Request.Content.IsMimeMultipartContent()) 
    { 
     //Save file 
     MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files")); 

     Request.Content.ReadAsMultipart(provider); // don't know if this is really valid. 

     return provider.BodyPartFileNames.First().Value; 
    } 
    else 
    { 
     return "Invalid."; 
    } 
} 

responsabilidad: realidad no he ejecutado el código anterior; Lo acabo de escribir para ilustrar lo que debería hacerse.

+2

No debe llamar a 'Wait()', ya que dará como resultado el bloqueo del hilo. En cambio, esta función 'UploadFile()' en sí misma debería ser asincrónica. – marcind

+0

Intenté su primera sugerencia, pero recibí un error en el método de inicio: No se puede llamar a Start en una tarea con acción nula. En cuanto a no asincrónicamente, no estoy seguro. No creo que haya un método como ReadAsMultipart (AFAIK). – Rivka

+0

@marcind Tienes razón, es una construcción incómoda. Es por eso que sugerí la alternativa síncrona, si es posible. Eso se debe a que tenemos dos tareas ejecutadas de forma secuencial y necesitamos control cuando estén terminadas. Creo que aquí no hay nada asincrónico. – GolfWolf

Cuestiones relacionadas