Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Request.Content.ReadAsMultipartAsync never returns

I have an API for a system written using the ASP.NET Web Api and am trying to extend it to allow images to be uploaded. I have done some googling and found how the recommended way to accept files using MultpartMemoryStreamProvider and some async methods but my await on the ReadAsMultipartAsync never returns.

Here is the 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);
    }
}

I can step through all the way to:

await Request.Content.ReadAsMultipartAsync(provider);

at which point it will never complete.

What is the reason why my await never returns?

Update

I am attempting to POST to this action using curl, the command is as follows:

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

I have also tried using the following html to POST to the action as well and the same thing happens:

<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>
like image 550
Jon Cahill Avatar asked Mar 04 '13 12:03

Jon Cahill


3 Answers

I ran into something similar in .NET 4.0 (no async/await). Using the debugger's Thread stack I could tell that ReadAsMultipartAsync was launching the task onto the same thread, so it would deadlock. I did something like this:

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

The TaskCreationOptions.LongRunning parameter was key for me because without it, the call would continue to launch the task onto the same thread. You could try using something like the following pseudocode to see if it works for you in C# 5.0:

await TaskEx.Run(async() => await Request.Content.ReadAsMultipartAsync(provider))
like image 123
yeejuto Avatar answered Nov 12 '22 21:11

yeejuto


I came across the same problem with all modern 4.5.2 framework.

My API method accepts one or more files uploaded using POST request with multipart content. It worked fine with small files, but with big ones, my method just hanged forever because the ReadAsMultipartAsync() function never completed.

What helped me: using an async controller method and await for the ReadAsMultipartAsync() to complete, instead of getting the task result in a synchronous controller method.

So, this did not work:

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

And this worked:

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

where SomeLogic is just a synchronous function taking binary content and producing a string (can be any kind of processing).

UPDATE And finally I've found the explanation in this article: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

The root cause of this deadlock is due to the way await handles contexts. By default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes. This “context” is the current SynchronizationContext unless it’s null, in which case it’s the current TaskScheduler. GUI and ASP.NET applications have a SynchronizationContext that permits only one chunk of code to run at a time. When the await completes, it attempts to execute the remainder of the async method within the captured context. But that context already has a thread in it, which is (synchronously) waiting for the async method to complete. They’re each waiting for the other, causing a deadlock.

So, basically, the “Async all the way” guideline has a reason behind it, and this is a good example.

like image 11
C-F Avatar answered Nov 12 '22 21:11

C-F


With help of another answer on stackoverflow and a blog post about targetFramework, I've found that updating to 4.5 and adding/updating the following in your web.config fixes this issue:

<system.web>
    <compilation debug="true" targetFramework="4.5"/>
</system.web>
<appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
like image 4
Zenuka Avatar answered Nov 12 '22 21:11

Zenuka