我有一个使用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>
答案 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 stackoverflow和a 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);