Request.Content.ReadAsMultipartAsync永远不会返回

Jon*_*ill 54 c# c#-5.0 asp.net-web-api

我有一个使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以一路走到:

await Request.Content.ReadAsMultipartAsync(provider);
Run Code Online (Sandbox Code Playgroud)

它永远不会完成.

我等待永远不会回来的原因是什么?

更新

我试图使用curl POST这个动作,命令如下:

C:\cURL>curl -i -F filedata=@C:\LowResExample.jpg http://localhost:8000/Api/Photos/89/LowResImage
Run Code Online (Sandbox Code Playgroud)

我也尝试使用以下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>
Run Code Online (Sandbox Code Playgroud)

yee*_*uto 96

我在.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();
Run Code Online (Sandbox Code Playgroud)

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

await TaskEx.Run(async() => await Request.Content.ReadAsMultipartAsync(provider))
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢分享这一点.有没有人知道为什么这是默认行为,如果它导致一个块? (10认同)
  • 优秀作品!我确实遇到了问题并由此解决了. (6认同)
  • 你救了我 我浪费了大约3天的时间.但有一件小事,如果一个人使用自定义的MultipartForDataStreamProvider,你实际上可以改变`StartNew` Action的内部来读取类似`provider = Request.Content.ReadAsMultipartAsync(provider).Result;`.然后,您可以在Wait函数之后访问提供程序. (3认同)
  • 谢谢!这个对我有用`await Task.FromResult(Request.Content.ReadAsMultipartAsync(provider));` (2认同)

C-F*_*C-F 8

我遇到了所有现代4.5.2框架的同样问题.

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

什么帮助我:使用async控制器的方法,并awaitReadAsMultipartAsync()完成,而不只是简单的同步控制器方法任务结果.

所以,这不起作用:

[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);
}
Run Code Online (Sandbox Code Playgroud)

这工作:

[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());
}
Run Code Online (Sandbox Code Playgroud)

其中SomeLogic只是一个同步函数,它采用二进制内容并生成一个字符串(可以是任何类型的处理).

更新最后我在本文中找到了解释:https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

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

所以,基本上,"异步一路"指南背后有一个原因,这是一个很好的例子.