Request.Content.ReadAsMultipartAsync никогда не возвращается
У меня есть API для системы, написанной с использованием ASP.NET Web 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);
после чего он никогда не завершится.
В чем причина моего ожидания никогда не возвращается?
Update
Я пытаюсь выполнить POST для этого действия с помощью curl, команда выглядит следующим образом:
C:\cURL>curl -i -F [email protected]:\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>
Ответы
Ответ 1
Я столкнулся с чем-то подобным в .NET 4.0 (нет async/await). Используя отладчик Thread stack, я мог сказать, что 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 был ключевым для меня, потому что без него вызов продолжал запускать задачу в тот же поток. Вы можете попробовать использовать что-то вроде следующего псевдокода, чтобы увидеть, работает ли он для вас в С# 5.0:
await TaskEx.Run(async() => await Request.Content.ReadAsMultipartAsync(provider))
Ответ 2
С помощью fooobar.com/questions/55901/... и сообщение в блоге о targetFramework, Я обнаружил, что обновление до версии 4.5 и добавление/обновление в вашем web.config устраняет эту проблему:
<system.web>
<compilation debug="true" targetFramework="4.5"/>
</system.web>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
Ответ 3
Я столкнулся с той же проблемой со всеми современными рамками 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
- это просто синхронная функция, принимающая двоичный контент и создающая строку (может быть любая обработка).
UPDATE И, наконец, я нашел объяснение в этой статье: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
Коренная причина этого тупика связана с тем, как ожидание обрабатывает контексты. По умолчанию, когда ожидается незавершенная задача, текущий "контекст" захватывается и используется для возобновления метода, когда задача завершается. Этот "контекст" представляет собой текущий SynchronizationContext, если его нулевой, и в этом случае его текущий TaskScheduler. Приложения GUI и ASP.NET имеют SynchronizationContext, который позволяет запускать только один кусок кода за раз. Когда ожидание завершается, он пытается выполнить оставшуюся часть асинхронного метода в захваченном контексте. Но этот контекст уже имеет в нем поток, который (синхронно) ожидает завершения асинхронного метода. Каждый из них ждет другого, вызывая тупик.
Итак, в основном, у руководства "Async all the way" есть причина, и это хороший пример.
Ответ 4
У меня есть рабочий проект .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;
}
Ответ 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);