Подтвердить что ты не робот

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

Ответ 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);