Request.Content.ReadAsMultipartAsync永远不会返回

时间:2013-03-04 12:09:09

标签: c# asp.net-web-api c#-5.0

我有一个使用ASP.NET Web Api编写的系统的API,我正在尝试扩展它以允许上传图像。我已经做了一些谷歌搜索,并找到了如何使用MultpartMemoryStreamProvider和一些异步方法接受文件的推荐方法,但我在ReadAsMultipartAsync上的等待永远不会返回。

以下是代码:

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

我可以一直走到:

await Request.Content.ReadAsMultipartAsync(provider);

此时它永远不会完成。

我的等待从未返回的原因是什么?

更新

我正在尝试使用curl对此操作进行POST,命令如下:

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

我也尝试使用以下html来POST动作,同样的事情发生了:

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

5 个答案:

答案 0 :(得分:95)

我在.NET 4.0中遇到过类似的东西(没有异步/等待)。使用调试器的Thread堆栈,我可以看出ReadAsMultipartAsync正在将任务启动到同一个线程上,因此它会死锁。我做了这样的事情:

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

TaskCreationOptions.LongRunning参数对我来说很关键,因为没有它,调用将继续将任务启动到同一个线程上。您可以尝试使用类似下面的伪代码来查看它是否适用于C#5.0:

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

答案 1 :(得分:8)

我遇到了与所有现代4.5.2框架相同的问题。

我的API方法接受使用带有多部分内容的POST请求上传的一个或多个文件。它适用于小文件,但是对于大文件,我的方法只是永远挂起,因为ReadAsMultipartAsync()函数从未完成。

帮助我的是什么:使用async控制器方法和await来完成ReadAsMultipartAsync(),而不是在同步控制器方法中获取任务结果

所以,这不起作用:

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

这有效:

[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只是一个采用二进制内容并生成字符串的同步函数(可以是任何类型的处理)。

更新最后我在本文中找到了解释:{{3}}

  

此死锁的根本原因是await处理上下文的方式。默认情况下,当等待未完成的任务时,捕获当前的“上下文”并用于在任务完成时恢复该方法。这个“上下文”是当前的SynchronizationContext,除非它是null,在这种情况下它是当前的TaskScheduler。 GUI和ASP.NET应用程序具有SynchronizationContext,一次只允许运行一个代码块。当await完成时,它会尝试在捕获的上下文中执行异步方法的剩余部分。但是该上下文已经有一个线程,它(同步)等待异步方法完成。他们每个人都在等着对方,造成僵局。

所以,基本上,“Async all as way”指南背后有一个原因,这是一个很好的例子。

答案 2 :(得分:4)

another answer on stackoverflowa blog post about targetFramework的帮助下,我发现更新到4.5并在web.config中添加/更新以下内容可以解决此问题:

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

答案 3 :(得分:0)

我有一个使用以下Post方法的.Net MVC WebAPi项目似乎运行良好。它与你已经非常相似,所以这应该会有所帮助。

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

答案 4 :(得分:0)

我也一样。我的解决方案

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

并调用

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

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