【问题标题】:Two parallel ajax requests to Action methods are queued, why?对 Action 方法的两个并行 ajax 请求排队,为什么?
【发布时间】:2016-11-06 09:26:02
【问题描述】:

我正在使用 ASP.NET MVC 开发一个视频网站。

我想在我的应用程序中拥有一个功能是转码视频。但由于转码过程可能非常耗时,我想向客户端用户展示该过程的进度。

因此,我的架构是使用一个控制器操作来处理整个转码过程,并将其进度写入存储在服务器上的文件中。同时我使用Ajax调用另一个控制器动作来读取指定的文件,检索进度信息并在转码过程中每2秒将其发送回客户端显示。

为了完成我的计划,我编写了以下代码:

服务器端:

public class VideoController : Controller
{
         //Other action methods
         ....
    //Action method for transcoding a video given by its id
    [HttpPost]
    public async Task<ActionResult> Transcode(int vid=0)
    {
        VideoModel VideoModel = new VideoModel();
        Video video = VideoModel.GetVideo(vid);
        string src = Server.MapPath("~/videos/")+video.Path;
        string trg = Server.MapPath("~/videos/") + +video.Id+".mp4";
        //The file that stores the progress information
        string logPath = Server.MapPath("~/videos/") + "transcode.txt";
        string pathHeader=Server.MapPath("../");

        if (await VideoModel.ConvertVideo(src.Trim(), trg.Trim(), logPath))
        {
            return Json(new { result = "" }); 
        }
        else
        {
          return Json(new { result = "Transcoding failed, please try again." });
        }
    }

    //Action method for retrieving the progress value from the specified log file
    public ActionResult GetProgress()
    {
        string logPath = Server.MapPath("~/videos/") + "transcode.txt";
        //Retrive the progress from the specified log file.
        ...
        return Json(new { progress = progress });
    }
}

客户端:

var progressTimer = null;
var TranscodeProgress = null;

// The function that requests server for handling the transcoding process
function Transcode(vid) {
    // Calls the Transcode action in VideoController
    var htmlobj = $.ajax({
        url: "/Video/Transcode",
        type: "POST",
        //dataType: 'JSON',
        data: { 'vid': vid },
        success: function(data)
        {
        if(data.result!="")
            alert(data.result);
        }
        else
        {
          //finalization works
          ....
        }
    }
    });
    //Wait for 1 seconds to start retrieving transcoding progress
    progressTimer=setTimeout(function ()
    {
        //Display progress bar
        ...
       //Set up the procedure of retrieving progress every 2 seconds
        TranscodeProgress = setInterval(Transcoding, 2000);
    }, 1000);
}

//The function that requests the server for retrieving the progress information every 2 seconds.
function Transcoding()
{
    //Calls the GetProgress action in VideoController
    $.ajax({
        url: "/Video/GetProgress",
    type: "POST",
    //dataType: 'JSON',
    success: function (data)
    {
        if (data.progress == undefined || data.progress == null)
            return;
        progressPerc = parseFloat(data.progress);
        //Update progress bar
        ...
    }
  });
}

现在客户端代码和Transcode 操作方法都可以正常工作。问题是在Transcode 操作完成其整个过程之前,永远不会调用GetProgress 方法。那么我的代码有什么问题?我如何修改它以使这两个动作自发地工作以实现我的目标?

更新

根据Alex的回答,我发现我的问题是由Asp.Net框架的会话锁定机制引起的。因此,禁用我的VideoControllerSessionState 或将其设置为只读确实会使控制器在执行转码视频的操作方法时响应检索转码进度的请求。但是因为我在VideoController 中使用Session 来存储一些变量以供跨多个请求使用,所以这种方式不适合我的问题。有没有更好的办法解决?

【问题讨论】:

  • 伊万,你应该接受@AlexArt。 `s answer,因为它解决了你的问题
  • @Menahem 当然我做到了。但我仍然需要为我的问题找到最佳解决方案。

标签: c# asp.net ajax asp.net-mvc asynchronous


【解决方案1】:

您误解了关于 async/await 的全部要点。它不会改变这样一个事实,即对于每个单个请求,都会返回一个响应。当您在操作中调用 await 时,尚未向客户端返回任何内容。它唯一做的事情(在非常高级的抽象中)是将处理此请求的当前线程释放到线程池,以便它可以用于处理其他请求。所以基本上它允许您更有效地使用您的服务器资源,因为没有浪费等待长时间 I/O 操作完成的线程。一旦 I/O 操作完成,将继续执行操作(需要等待)。只有在操作结束时才会将响应发送到客户端。

对于你的场景,如果它是一个长时间运行的任务,我会使用某种后台处理解决方案,例如Hangfire,并使用 SignalR 从服务器推送更新。Here is an example

您也可以自己实现类似的东西 (example)。

更新: 正如@Menahem 在他的评论中所说,我可能误解了你的部分问题。

请求队列问题可能是由SessionStateBehavior 的错误配置引起的。由于 ASP.NET MVC 使用的MvcHandler 处理程序标有IRequiresSessionState 接口,因此每个会话一次只能处理一个请求。为了改变这一点,让你的控制器无会话(或者至少确保你没有在这个控制器中写入会话)并将其标记为 [SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)] 属性。

【讨论】:

  • 您可能对 OP 出错的地方是正确的,但请注意,他是在客户端使用 Ajax 来实现 2 个请求的并行处理。所以这不是他的请求被排队的原因。
  • @Menahem,我明白你的意思。将相应地更新答案
  • 我通过将[SessionState(SessionStateBehavior.Disabled)] 放在VideoController 上方来测试它,当Transcode 操作正在进行时,GetProgressaction 确实会被调用。所以我得到了导致我的问题的原因。但是由于我使用TempData,它在VideoController 内部使用Session 来存储我需要跨请求使用的一些变量,将整个控制器设置为只读Session 可能不适合我的情况。
  • 您可以使用基于 cookie 的 TempDataProvider 而不是默认的 brockallen.com/2012/06/11/cookie-based-tempdata-provider
  • 可以新建一个单独的控制器
【解决方案2】:

文件创建阻塞调用。换句话说,在第一个线程不会关闭文件之前,第二个生成报告的线程将无法读取该文件的内容。作为解决方法,您可以创建具有进度百分比的文件。例如movie1-5-percent.txt、movie1-10-percent.txt、movie1-15-percent.txt等,以避免阻塞对文件系统的调用。然后你可以检查,如果movie1 有movie1-15-percent.txt 文件,那么你可以向ajax 调用报告,15% 的电影被转换了。或者选择另一个非阻塞存储。例如,您可以在第一个线程中向 db 报告结果,并在另一个线程中从 db 中读取结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-17
    • 2011-03-03
    相关资源
    最近更新 更多