【问题标题】:Request.Content.ReadAsMultipartAsync never returnsRequest.Content.ReadAsMultipartAsync 永远不会返回
【发布时间】:2013-02-18 12:33:28
【问题描述】:

我有一个使用 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);

到那时它永远不会完成。

我的 await 永远不会返回的原因是什么?

更新

我正在尝试使用 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>

【问题讨论】:

  • 你的客户端代码是什么样子的?
  • 您能分享一下您的原始请求是什么样的吗?
  • await之后的代码中也放一个断点。有时,当您使用 async/await 时,它不会中断/跳到下一行(根据我的经验)
  • hmm...我尝试使用相同的代码和上面的 html,但我没有看到您提到的问题...
  • 我来自 asp.net web api 团队。如果您可以将您的复制项目发送给我,我将有兴趣进行更多调试。我的邮箱:kiranchalla@hotmail.com

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


【解决方案1】:

我在 .NET 4.0 中遇到了类似的问题(没有异步/等待)。使用调试器的线程堆栈,我可以看出 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))

【讨论】:

  • 出色的工作!我正好遇到了这个问题并通过这个解决了它。
  • 我正在使用 Web API,我需要在构造函数中调用异步方法。这是我设法使它工作的唯一方法!谢谢!
  • 非常感谢您分享此内容。如果导致阻塞,有谁知道为什么这是默认行为?
  • 你救了我。我在这上面浪费了大约 3 天。不过有一件小事,如果使用自定义的 MultipartForDataStreamProvider,您实际上可以将 StartNew Action 的内部更改为类似 provider = Request.Content.ReadAsMultipartAsync(provider).Result; 的内容。然后您可以在等待功能之后访问提供程序。
  • 谢谢!这个对我有用await Task.FromResult(Request.Content.ReadAsMultipartAsync(provider));
【解决方案2】:

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

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

        [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】:

      我在所有现代 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 只是一个同步函数,获取二进制内容并生成字符串(可以是任何类型的处理)。

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

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

      所以,基本上,“一路异步” 准则背后有其原因,这是一个很好的例子。

      【讨论】:

        【解决方案5】:

        我也一样。我的解决方案

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

        【讨论】:

          猜你喜欢
          • 2019-11-15
          • 2011-07-25
          • 1970-01-01
          • 2012-10-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多