Как заблокировать длинный асинхронный вызов в действии WebApi?

У меня есть этот сценарий, в котором у меня есть WebApi и конечная точка, которая при запуске выполняет большую работу (около 2-5 минут). Это конечная точка POST с побочными эффектами, и я хотел бы ограничить выполнение, чтобы, если 2 запроса были отправлены в эту конечную точку (не должно происходить, но лучше перестраховаться), один из них должен был ждать, чтобы избежать гонки условия.

Сначала я попытался использовать простую статическую блокировку внутри контроллера следующим образом:

lock (_lockObj)
{
    var results = await _service.LongRunningWithSideEffects();
    return Ok(results);
}

это, конечно, невозможно из-за await внутри оператора lock.

Другое решение, которое я рассматривал, заключалось в использовании реализации SemaphoreSlim следующим образом:

await semaphore.WaitAsync();
try
{
    var results = await _service.LongRunningWithSideEffects();
    return Ok(results);
}
finally 
{
    semaphore.Release();
}

Однако, согласно MSDN:

Класс SemaphoreSlim представляет собой легкий и быстрый семафор, который можно использовать для ожидания внутри одного процесса, когда ожидается, что время ожидания будет очень коротким.

Так как в этом сценарии время ожидания может достигать 5 минут, что мне использовать для контроля параллелизма?

РЕДАКТИРОВАТЬ (в ответ на plog17):

Я понимаю, что передача этой задачи службе может быть оптимальным способом, однако я не обязательно хочу ставить в очередь что-то в фоновом режиме, которое все еще выполняется после выполнения запроса. Запрос включает в себя другие запросы и интеграции, которые занимают некоторое время, но я все же хотел бы, чтобы пользователь дождался завершения этого запроса и получил ответ в любом случае. Ожидается, что этот запрос будет запускаться только один раз в день в определенное время заданием cron. Тем не менее, есть также возможность запустить его вручную разработчиком (в основном, если что-то пойдет не так с заданием), и я хотел бы убедиться, что API не столкнется с проблемами параллелизма, если разработчик, например. дважды отправляет запрос случайно и т. д.


person valorl    schedule 07.02.2017    source источник


Ответы (1)


Если в данный момент времени может быть обработан только один запрос такого рода, почему бы не реализовать очередь?

С таким дизайном больше не нужно блокировать или ждать при обработке длительного запроса.

Поток может быть:

  1. Клиент POST /RessourcesToProcess должен быстро получить 202-Accepted
  2. HttpController просто ставит задачу в очередь для продолжения (и возвращает 202-accepted)

  3. Другая служба (служба Windows?) удаляет следующую задачу из очереди, чтобы продолжить

  4. Приступить к задаче
  5. Обновить статус ресурса

Во время этого процесса клиент должен легко получить статус ранее сделанных запросов:

  • Если задача не найдена: 404-NotFound. Ресурс не найден для идентификатора 123
  • При обработке задачи: 200-ОК. 123 обрабатывается.
  • Если задача выполнена: 200-ОК. Ответ процесса.

Ваш контроллер может выглядеть так:

public class TaskController
{

    //constructor and private members

    [HttpPost, Route("")]
    public void QueueTask(RequestBody body)
    {
        messageQueue.Add(body);
    }

    [HttpGet, Route("taskId")]
    public void QueueTask(string taskId)
    {
        YourThing thing = tasksRepository.Get(taskId);

        if (thing == null)
        {
            return NotFound("thing does not exist");
        }
        if (thing.IsProcessing)
        {
            return Ok("thing is processing");
        }
        if (!thing.IsProcessing)
        {
            return Ok("thing is not processing yet");
        }
        //here we assume thing had been processed
        return Ok(thing.ResponseContent);
    }
}

Этот дизайн предполагает, что вы не обрабатываете длительный процесс внутри своего WebApi. На самом деле, это может быть не лучший выбор дизайна. Если вы все еще хотите это сделать, вы можете прочитать:

person plog17    schedule 08.02.2017
comment
Из-за ограничения количества символов в комментариях я ответил в виде редактирования, поэтому, пожалуйста, ознакомьтесь с обновленным вопросом. - person valorl; 10.02.2017