Вопрос: Потоковый вывод текста для длительных действий?


У меня есть несколько действий утилиты, которые возвращают вывод текста через return Content("my text","text/plain"),

Иногда эти методы занимают несколько минут для запуска (т. Е. Синтаксический анализ журналов, обслуживание базы данных).

Я хотел бы изменить свой метод действий, чтобы вместо того, чтобы сразу возвращать весь вывод, текст будет передан потоку клиенту, когда он будет готов.

Вот надуманный пример:

public ActionResult SlowText()
{
    var sb = new System.Text.StringBuilder();
    sb.AppendLine("This happens quickly...");
    sb.AppendLine("Starting a slow 10 second process...");
    System.Threading.Thread.Sleep(10000);
    sb.AppendLine("All done with 10 second process!");
    return Content(sb.ToString(), "text/plain");
}

Как написано, это действие вернет три строки текста через 10 секунд. То, что я хочу, это способ сохранить поток ответа открытым и сразу же вернуть первые две строки, а затем третью строку через 10 секунд.

Я помню, как это делал 10 лет назад в Classic ASP 3.0 с использованием объекта Response. Есть ли официальный способ MVC для этого?

-

Обновление: использование Razor .cshtml в приложении; но не используя какие-либо представления (просто ContentResult) для этих действий.


16


источник


Ответы:


Написание непосредственно на Response объект должен работать, но только в некоторых простых случаях. Многие функции MVC зависят от замены замещения вывода (например, частичные представления, механизм просмотра Razor и т. Д.), И если вы будете писать непосредственно в ответ, ваш результат будет неработоспособным.

Однако, если вы не используете представление и вместо этого пишете прямо в контроллере, тогда вы должны быть в порядке (если ваше действие не вызывается как дочернее действие).


5



Я бы пропустил контроллер MVC полностью, так как вы все равно сломаете инкапсуляцию. В этом месте я бы использовал реализацию Barenaked IHttpHandler, потоковое прямое отношение к вышеупомянутому потоку вывода.


1



Вы подвергаете себя тайм-ауту браузера, если процесс занимает больше времени, чем первоначально предполагалось. Тогда у вас нет способа восстановить то, что произошло / если вы не реализуете отдельный метод, который дает информацию о длительном процессе.

Учитывая, что вы хотите другой метод в любом случае, вы можете начать длительный процесс и немедленно вернуться. Попросите браузер проверить другой метод, который дает самую последнюю информацию о продолжительном процессе. В последний раз я должен был сделать это, я оставил его простым и просто установил заголовок обновления из контроллера, прежде чем возвращать представление.

Что касается начала долгого процесса, вы можете сделать что-то вроде этого:

// in the controller class
delegate void MyLongProcess(); 
//...
// in the method that starts the action
MyLongProcess processTask = new MyLongProcess(_someInstance.TheLongRunningImplementation);
processTask.BeginInvoke(new AsyncCallback(EndMyLongProcess), processTask);
//...
public void EndMyLongProcess(IAsyncResult result)
{
   try{
      MyLongProcess processTask = (MyLongProcess)result.AsyncState;
      processTask.EndInvoke(result);
      // anything you needed at the end of the process
   } catch(Exception ex) {
      // an error happened, make sure to log this 
      // as it won't hit the global.asax error handler                    
   }
}

Что касается того, где вы помещаете журнал действий, которые произошли, зависит от вас, как долго вы проживаете, как хотите. Он может быть таким же простым, как статическое поле / класс, в котором вы добавляете информацию о текущем процессе или вместо этого сохраняете его в хранилище данных, где он может пережить повторное использование приложения.

Вышеизложенное предполагает, что все это связано с длительным процессом, который продолжает сообщать о действиях, которые были сделаны. Потоковая передача - это другой вопрос, но вышеупомянутое может по-прежнему играть роль в поддержании операций в вашем контроллере, и только часть, ответственная за потоковое вещание, становится доступной клиенту в результате действия.


1



Вы можете реализовать свой собственный ActionResult, например ContentStreamingResult, и использовать HttpContext, HttpRequest и HttpResponse в методе ExecuteResult.

public class ContentStreamingResult : ActionResult
    {
        private readonly TextReader _reader;

        public ContentStreamingResult(TextReader reader)
        {
            _reader = reader;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            var httpContext = context.HttpContext;
            //Read text from the reader and write to the response
        }
    }

public class YourController : Controller
    {
        public ContentStreamingResult DownloadText()
        {
            string text = "text text text";
            return new ContentStreamingResult(new System.IO.StringReader(text));
        }
    }

1



Пытаться Response.Flush а также BufferOutput в false, Обратите внимание, что это будет работать с различными результатами действий, вы должны напрямую писать в response объект. Вероятно, вы можете использовать его вместе с AsyncController,


0