两个并行的ajax请求到Action方法被排队了,为什么?

6

我正在使用 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”操作方法都正常工作。问题是,“GetProgress”方法只有在“Transcode”操作完成整个过程之后才会被调用。那么我的代码哪里出了问题?我该如何修改它,使这两个操作可以自动工作,以实现我的目标?
更新
根据Alex的答案,我发现我的问题是由于Asp.Net框架的会话锁机制引起的。因此,禁用我的“VideoController”的“SessionState”或将其设置为只读确实可以使控制器在执行视频转码操作的操作方法时响应检索转码进度的请求。但是,因为我在我的“VideoController”中使用“Session”来存储一些跨多个请求使用的变量,所以这种方式不能成为解决我的问题的合适解决方案。有没有更好的方法来解决它?

Ivan,你应该接受@AlexArt的答案,因为它解决了你的问题。 - Menahem
@Menahem 当然了,我已经尝试过了。但是我仍然需要找到最优解决方案来解决我的问题。 - Ivan
2个回答

10
你误解了async/await的整个意义。它并不改变每个请求都有一个返回的事实。当你在操作中调用await时,还没有返回任何东西给客户端。它唯一做的事情(在高层抽象中)是将处理此请求的当前线程释放到线程池中,以便可以用于处理其他请求。因此,它基本上允许你更有效地使用服务器资源,因为没有线程浪费在等待长时间I/O操作完成上。一旦I/O操作完成,调用await的操作(即继续执行)才会继续执行。只有在操作结束时,响应才会发送到客户端。
至于你的场景,如果它是一个长时间运行的任务,我会使用一些后台处理解决方案,比如Hangfire,并使用SignalR从服务器推送更新。这里有一个例子 你也可以自己实现类似的功能(例子)。

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

请求排队问题可能是由SessionStateBehavior配置不正确引起的。由ASP.NET MVC使用的MvcHandler处理程序标记有IRequiresSessionState接口,每个会话只能同时处理一个请求。为了改变这种情况,请使你的控制器无状态(或者至少确保在此控制器中没有写入会话),并使用[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]属性对其进行标记。


你对问题的提出可能是正确的,但需要注意的是,他在客户端使用Ajax来实现两个请求的并行处理。因此,这并不是为什么他的请求被排队的答案。 - Menahem
@Menahem,我明白你的意思。我会相应地更新答案。 - Alex Art.
我通过在VideoController上方放置[SessionState(SessionStateBehavior.Disabled)]进行了测试,当Transcode操作正在进行时,GetProgress动作确实被调用。所以我找到了导致问题的原因。但是,由于我在VideoController中使用了TempData来存储一些需要跨请求使用的变量,将整个控制器设置为只读Session可能不适合我的情况。 - Ivan
1
你可以使用基于Cookie的TempDataProvider代替默认的TempDataProvider https://brockallen.com/2012/06/11/cookie-based-tempdata-provider/ - Alex Art.
我刚刚对我的 VideoController 进行了深入检查,发现我在其他几个操作方法中也使用了 Session - Ivan
1
您可以创建一个新的独立控制器。 - Alex Art.

1
文件创建是阻塞调用。换句话说,除非第一个线程关闭文件,否则生成报告的第二个线程将无法读取该文件的内容。作为解决方法,您可以按进度创建文件。例如,movie1-5-percent.txt、movie1-10-percent.txt、movie1-15-percent.txt等,以避免对文件系统的阻塞调用。然后,您可以检查是否有movie1-15-percent.txt这样的文件,如果有,则可以向ajax调用报告,已转换电影的15%。或者选择另一个非阻塞存储。例如,您可以在第一个线程中向数据库报告结果,并在另一个线程中从数据库中读取结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接