Request.Content.ReadAsMultipartAsync ne renvoie jamais

J’ai une API pour un système écrit à l’aide de l’interface Web ASP.NET et j’essaie de l’étendre pour permettre le téléchargement d’images. J’ai fait des recherches sur Google et j’ai découvert que la méthode recommandée pour accepter des fichiers à l’aide de MultpartMemoryStreamProvider et de certaines méthodes asynchrones, mais que j’attendais sur ReadAsMultipartAsync, ne retourne jamais.

Voici le code:

[HttpPost] public async Task LowResImage(int id) { if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var provider = new MultipartMemoryStreamProvider(); try { await Request.Content.ReadAsMultipartAsync(provider); foreach (var item in provider.Contents) { if (item.Headers.ContentDisposition.FileName != null) { } } return Request.CreateResponse(HttpStatusCode.OK); } catch (System.Exception e) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e); } } 

Je peux parcourir tout le chemin pour:

 await Request.Content.ReadAsMultipartAsync(provider); 

à quel point il ne sera jamais complet.

Quelle est la raison pour laquelle mon attente ne revient jamais?

Mettre à jour

Je tente de POST à ​​cette action en utilisant curl, la commande est la suivante:

 C:\cURL>curl -i -F filedata=@C:\LowResExample.jpg http://localhost:8000/Api/Photos/89/LowResImage 

J’ai également essayé d’utiliser le code HTML suivant pour POST à ​​l’action et la même chose se produit:

 

J’ai rencontré quelque chose de similaire dans .NET 4.0 (pas d’async / wait). En utilisant la stack de threads du débogueur, je pouvais dire que ReadAsMultipartAsync lançait la tâche sur le même thread, de sorte qu’il serait bloqué. J’ai fait quelque chose comme ça:

 IEnumerable parts = null; Task.Factory .StartNew(() => parts = Request.Content.ReadAsMultipartAsync().Result.Contents, CancellationToken.None, TaskCreationOptions.LongRunning, // guarantees separate thread TaskScheduler.Default) .Wait(); 

Le paramètre TaskCreationOptions.LongRunning était la clé pour moi car sans cela, l’appel continuerait à lancer la tâche sur le même thread. Vous pouvez essayer d’utiliser le pseudo-code suivant pour voir si cela fonctionne pour vous dans C # 5.0:

 await TaskEx.Run(async() => await Request.Content.ReadAsMultipartAsync(provider)) 

J’ai rencontré le même problème avec tous les frameworks 4.5.2 modernes.

Ma méthode API accepte un ou plusieurs fichiers téléchargés à l’aide d’une requête POST avec un contenu en plusieurs parties. Cela fonctionnait bien avec les petits fichiers, mais avec les gros fichiers, ma méthode venait juste d’être ReadAsMultipartAsync() pour toujours car la fonction ReadAsMultipartAsync() n’était jamais terminée.

Ce qui m’a aidé: utiliser une méthode de contrôleur async et await que le ReadAsMultipartAsync() se termine, au lieu d’obtenir le résultat de la tâche dans une méthode de contrôleur synchrone.

Donc, cela n’a pas fonctionné:

 [HttpPost] public IHttpActionResult PostFiles() { return Ok ( Request.Content.ReadAsMultipartAsync().Result .Contents .Select(content => ProcessSingleContent(content)) ); } private ssortingng ProcessSingleContent(HttpContent content) { return SomeLogic(content.ReadAsByteArrayAsync().Result); } 

Et cela a fonctionné:

 [HttpPost] public async Task PostFiles() { return Ok ( await Task.WhenAll ( (await Request.Content.ReadAsMultipartAsync()) .Contents .Select(async content => await ProcessSingleContentAsync(content)) ) ); } private async Task ProcessSingleContentAsync(HttpContent content) { return SomeLogic(await content.ReadAsByteArrayAsync()); } 

SomeLogic est juste une fonction synchrone prenant du contenu binary et produisant une chaîne (peut être n’importe quel traitement).

MISE À JOUR Et finalement, j’ai trouvé l’explication dans cet article: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

La cause première de cette impasse est due à la manière dont l’attente gère les contextes. Par défaut, lorsqu’une tâche incomplète est attendue, le «contexte» actuel est capturé et utilisé pour reprendre la méthode à la fin de la tâche. Ce «contexte» est le SynchronizationContext actuel sauf s’il est nul, auquel cas il s’agit du TaskScheduler actuel. Les applications GUI et ASP.NET ont un SynchronizationContext qui permet à un seul morceau de code de s’exécuter à la fois. Lorsque l’attente est terminée, il tente d’exécuter le rest de la méthode asynchrone dans le contexte capturé. Mais ce contexte contient déjà un thread qui attend (de manière synchrone) la fin de la méthode asynchrone. Ils attendent chacun l’autre, provoquant une impasse.

Donc, fondamentalement, la directive «Async all the way» a une raison derrière, et c’est un bon exemple.

Avec l’aide d’ une autre réponse sur stackoverflow et d’ un article sur targetFramework , j’ai constaté que la mise à jour vers la version 4.5 et l’ajout / la mise à jour de ce qui suit dans votre fichier web.config résout ce problème:

       

Je travaille sur un projet Web .NET MVC avec la méthode Post qui semble bien fonctionner. C’est très similaire à ce que vous avez déjà, donc cela devrait être utile.

  [System.Web.Http.AcceptVerbs("Post")] [System.Web.Http.HttpPost] public Task Post() { // Check if the request contains multipart/form-data. if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } ssortingng fileSaveLocation = @"c:\SaveYourFile\Here\XXX"; CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(fileSaveLocation); Task task = Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception); } foreach (MultipartFileData file in provider.FileData) { //Do Work Here } return Request.CreateResponse(HttpStatusCode.OK); } ); return task; } 

J’ai eu le même. Ma solution

 public List UploadFiles(HttpFileCollection fileCollection) { var uploadsDirectoryPath = HttpContext.Current.Server.MapPath("~/Uploads"); if (!Directory.Exists(uploadsDirectoryPath)) Directory.CreateDirectory(uploadsDirectoryPath); var filePaths = new List(); for (var index = 0; index < fileCollection.Count; index++) { var path = Path.Combine(uploadsDirectoryPath, Guid.NewGuid().ToString()); fileCollection[index].SaveAs(path); filePaths.Add(path); } return filePaths; } 

et invoquer

 if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var filePaths = _formsService.UploadFiles(HttpContext.Current.Request.Files);