web-dev-qa-db-fra.com

Request.Content.ReadAsMultipartAsync ne renvoie jamais

J'ai une API pour un système écrit à l'aide de l'API Web ASP.NET et j'essaie de l'étendre pour permettre le téléchargement des images. J'ai fait quelques recherches sur Google et trouvé comment la façon recommandée d'accepter des fichiers à l'aide de MultpartMemoryStreamProvider et de certaines méthodes asynchrones, mais mon attente sur ReadAsMultipartAsync ne revient jamais.

Voici le code:

[HttpPost]
public async Task<HttpResponseMessage> 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 avancer jusqu'à:

await Request.Content.ReadAsMultipartAsync(provider);

à quel point il ne sera jamais terminé.

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

Mise à jour

J'essaie 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 également et la même chose se produit:

<form method="POST" action="http://localhost:8000/Api/Photos/89/LowResImage" enctype="multipart/form-data">
    <input type="file" name="fileupload"/>
    <input type="submit" name="submit"/>
</form>
54
Jon Cahill

J'ai rencontré quelque chose de similaire dans .NET 4.0 (pas d'async/attente). En utilisant la pile de threads du débogueur, je pouvais dire que ReadAsMultipartAsync lançait la tâche sur le même thread, donc elle se bloquerait. J'ai fait quelque chose comme ça:

IEnumerable<HttpContent> 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 lui, l'appel continuerait de lancer la tâche sur le même thread. Vous pouvez essayer d'utiliser quelque chose comme le pseudocode suivant pour voir si cela fonctionne pour vous en C # 5.0:

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

Je suis tombé sur 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 de la commande POST avec un contenu en plusieurs parties. Cela fonctionnait bien avec les petits fichiers, mais avec les gros, ma méthode a juste été bloquée pour toujours parce que la fonction ReadAsMultipartAsync() fonction jamais terminée.

Ce qui m'a aidé: en utilisant une méthode de contrôleur async et await pour que la fonction 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 string ProcessSingleContent(HttpContent content)
{
    return SomeLogic(content.ReadAsByteArrayAsync().Result);
}

Et cela a fonctionné:

[HttpPost]
public async Task<IHttpActionResult> PostFiles()
{
    return Ok
    (
        await Task.WhenAll
        (
            (await Request.Content.ReadAsMultipartAsync())

            .Contents
            .Select(async content => await ProcessSingleContentAsync(content))  
        )
    );
}

private async Task<string> ProcessSingleContentAsync(HttpContent content)
{
    return SomeLogic(await content.ReadAsByteArrayAsync());
}

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

[~ # ~] mise à jour [~ # ~] Et enfin j'ai trouvé l'explication dans cet article: https: // msdn .Microsoft.com/en-us/magazine/jj991977.aspx

La cause première de ce blocage est due à la façon dont wait 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 une fois la tâche terminée. 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. Une fois l'attente terminée, il tente d'exécuter le reste de la méthode async dans le contexte capturé. Mais ce contexte contient déjà un thread, qui attend (de manière synchrone) la fin de la méthode async. Ils attendent chacun l'autre, provoquant une impasse.

Donc, fondamentalement, la directive "Async tout le chemin" a une raison derrière cela, et ceci est un bon exemple.

8
C-F

Avec l'aide de ne autre réponse sur stackoverflow et n article de blog sur targetFramework , j'ai trouvé que la mise à jour vers 4.5 et l'ajout/mise à jour des éléments suivants dans votre web.config corrige ce problème :

<system.web>
    <compilation debug="true" targetFramework="4.5"/>
</system.web>
<appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
4
Zenuka

J'avais la même chose. Ma solution

public List<string> UploadFiles(HttpFileCollection fileCollection)
    {
        var uploadsDirectoryPath = HttpContext.Current.Server.MapPath("~/Uploads");
        if (!Directory.Exists(uploadsDirectoryPath))
            Directory.CreateDirectory(uploadsDirectoryPath);

        var filePaths = new List<string>();

        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);
0
Mykhailo Kmet

J'ai un projet .Net MVC Web APi fonctionnel avec la méthode Post suivante qui semble bien fonctionner. C'est très similaire à ce que vous avez déjà, cela devrait donc être utile.

    [System.Web.Http.AcceptVerbs("Post")]
    [System.Web.Http.HttpPost]
    public Task<HttpResponseMessage> Post()
    {
        // Check if the request contains multipart/form-data.
        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }
        string fileSaveLocation = @"c:\SaveYourFile\Here\XXX";
        CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(fileSaveLocation);
        Task<HttpResponseMessage> task = Request.Content.ReadAsMultipartAsync(provider).ContinueWith<HttpResponseMessage>(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;
    }
0
Camille Sévigny