webentwicklung-frage-antwort-db.com.de

Request.Content.ReadAsMultipartAsync kehrt nie zurück

Ich habe eine API für ein System, das mit der ASP.NET-Web-API geschrieben wurde, und versuche, sie so zu erweitern, dass Bilder hochgeladen werden können. Ich habe ein bisschen gegoogelt und herausgefunden, wie die empfohlene Methode zum Akzeptieren von Dateien mit MultpartMemoryStreamProvider und einigen asynchronen Methoden funktioniert, aber mein Warten auf ReadAsMultipartAsync kehrt nie zurück.

Hier ist der 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);
    }
}

Ich kann den ganzen Weg gehen, um:

await Request.Content.ReadAsMultipartAsync(provider);

an diesem Punkt wird es niemals abgeschlossen sein.

Was ist der Grund, warum mein Warten niemals zurückkehrt?

Aktualisieren

Ich versuche, POST mit curl auf diese Aktion zuzugreifen. Der Befehl lautet wie folgt:

C:\cURL>curl -i -F [email protected]:\LowResExample.jpg http://localhost:8000/Api/Photos/89/LowResImage

Ich habe auch versucht, das folgende HTML zu verwenden, um POST zu der Tätigkeit außerdem und das selbe geschieht:

<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

Ich bin in .NET 4.0 auf etwas Ähnliches gestoßen (kein async/await). Unter Verwendung des Thread-Stacks des Debuggers konnte ich feststellen, dass ReadAsMultipartAsync die Aufgabe auf demselben Thread startete, sodass sie zum Stillstand kam. Ich habe so etwas gemacht:

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

Der Parameter TaskCreationOptions.LongRunning war der Schlüssel für mich, da ohne ihn der Aufruf die Aufgabe weiterhin auf demselben Thread startet. Sie können versuchen, den folgenden Pseudocode zu verwenden, um festzustellen, ob er in C # 5.0 für Sie funktioniert:

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

Ich bin auf dasselbe Problem mit allen modernen 4.5.2-Frameworks gestoßen.

Meine API-Methode akzeptiert eine oder mehrere Dateien, die mit POST request with multipart content) hochgeladen wurden. Bei kleinen Dateien funktionierte sie einwandfrei, bei großen jedoch blieb meine Methode für immer hängen, da die Funktion ReadAsMultipartAsync() Funktion nie abgeschlossen.

Was mir geholfen hat: Verwenden einer async-Controllermethode und await, damit ReadAsMultipartAsync() abgeschlossen wird , anstatt die Aufgabe in einer synchronen Controller-Methode zu erhalten.

Also, das hat nicht funktioniert:

[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);
}

Und das hat funktioniert:

[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());
}

dabei ist SomeLogic nur eine synchrone Funktion, die binären Inhalt aufnimmt und einen String erzeugt (kann jede Art von Verarbeitung sein).

[~ # ~] update [~ # ~] Und schließlich habe ich die Erklärung in diesem Artikel gefunden: https: // msdn .Microsoft.com/de-de/magazine/jj991977.aspx

Die Grundursache für diesen Deadlock liegt in der Art und Weise, wie das Warten auf Kontexte abläuft. Wenn auf eine unvollständige Aufgabe gewartet wird, wird standardmäßig der aktuelle „Kontext“ erfasst und verwendet, um die Methode fortzusetzen, wenn die Aufgabe abgeschlossen ist. Dieser "Kontext" ist der aktuelle SynchronizationContext, sofern er nicht null ist. In diesem Fall ist er der aktuelle TaskScheduler. GUI- und ASP.NET-Anwendungen verfügen über einen SynchronizationContext, mit dem jeweils nur ein Codeabschnitt ausgeführt werden kann. Nach Abschluss des Wartens wird versucht, den Rest der asynchronen Methode im erfassten Kontext auszuführen. In diesem Kontext befindet sich jedoch bereits ein Thread, der (synchron) auf den Abschluss der asynchronen Methode wartet. Sie warten aufeinander und verursachen einen Deadlock.

Im Grunde hat die "Async all the way" -Richtlinie einen Grund, und dies ist ein gutes Beispiel.

8
C-F

Mithilfe von eine weitere Antwort auf stackoverflow und ein Blogbeitrag über targetFramework habe ich festgestellt, dass das Aktualisieren auf 4.5 und das Hinzufügen/Aktualisieren des Folgenden in Ihrer web.config dieses Problem behebt :

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

Ich hatte das selbe Meine Lösung

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;
    }

und aufrufen

if (!Request.Content.IsMimeMultipartContent())
{
    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}

var filePaths = _formsService.UploadFiles(HttpContext.Current.Request.Files);
0
Mykhailo Kmet

Ich habe ein funktionierendes .Net MVC Web APi-Projekt mit der folgenden Post-Methode, die anscheinend gut funktioniert. Es ist sehr ähnlich zu dem, was Sie bereits haben, daher sollte dies hilfreich sein.

    [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