【问题标题】:Progress bar with PHP & AjaxPHP 和 Ajax 的进度条
【发布时间】:2012-03-07 21:25:36
【问题描述】:

我正在使用 ajax 请求和会话变量来更新进度条。当我的程序执行耗时的操作(例如发送许多电子邮件等)时,它只需设置正确的会话变量(其中包含进度值)。此操作由下面代码中的函数 post() 启动。

同时,第二个函数 ask() 每 500 毫秒循环执行一次。它应该实时返回当前进度。这就是问题所在:ask() 发送的每个请求都在等待 post() 函数发送的请求完成。有趣的是,如果我设置一些像 google.com 这样的 URL 而不是 url/to/progress 它工作得很好,只是它不是我想要的:)。这意味着问题出在服务器端。

不确定它是否重要,但我使用的是 Yii 框架。

下面的所有代码都只是草稿(但可以工作),其唯一目的是说明我的意思。

提前致谢。

对不起我的英语不好:)

查看部分:

<script type="text/javascript">
function ask() {
  var d = new Date();
  var time = d.getTime();
  $.ajax({

    type: 'get',
    url: '/url/to/progress' + '?time=' + time,
    success: function(data) {
      $("#progress").html(data);
    }
  })
}

function post() {
  var d = new Date();
  var time = d.getTime();

  $.ajax({
      type: 'post',
      url: '/url/to/post' + '?time=' + time,
      data: {"some": "data"},
      success: function(data) {alert(data)}
    });
}

$("#test").click(
  function() {
    post();
    var progress = setInterval("ask();", 500);
  }
);
</script>

控制器部分:

public function actionPost($time) {
  sleep(5); // time consuming operation
  echo $time . ' : ' . microtime();
  exit;
}

public function actionProgress($time) {
  echo $time . ' : ' . microtime();
  exit;
}

【问题讨论】:

  • 您从服务器得到的响应是什么?检查开发工具 > 网络 > xhr 请求

标签: php jquery post request progress


【解决方案1】:

我认为您的问题与会话有关。

当脚本有一个打开的会话时,它会锁定会话文件。这意味着任何使用相同会话 ID 的后续请求都将排队,直到第一个脚本释放它对会话文件的锁定。您可以使用 session_write_close() 强制执行此操作 - 但这对您没有帮助,因为您正在尝试与会话文件共享进度信息,因此 post 脚本需要保持会话数据打开和可写。

您需要想出另一种在postprogress 脚本之间共享数据的方式 - 如果post 在其执行过程中打开会话数据,progress 将永远无法访问会话直到post 完成执行之后的数据。也许您可以使用会话 ID 创建一个临时文件,post 可以写入该文件,您可以在其中放置进度指示器数据。 progress 可以检查文件并返回该数据。 IPC(进程间通信)有很多选择 - 这不是一个特别漂亮的选择,但它确实具有最大可移植性的优势。

附带说明 - 请不要将字符串传递给 setInterval(),传递函数。所以你的行实际上应该是:

var progress = setInterval(ask, 500);

但是 - 在ask() ajax 函数的success/error 处理程序中使用setTimeout() 会更好。这是因为通过使用setInterval(),无论前一个请求的状态如何,都会发起一个新请求。等到前一个请求完成后再启动下一个请求会更有效率。所以我会做更多这样的事情:

<script type="text/javascript">

  // We'll set this to true when the initail POST request is complete, so we
  // can easily know when to stop polling the server for progress updates
  var postComplete = false;

  var ask = function() {

    var time = new Date().getTime();

    $.ajax({

      type: 'get',
      url: '/url/to/progress' + '?time=' + time,

      success: function(data) {
        $("#progress").html(data);
        if (!postComplete)
          setTimeout(ask, 500);
        }
      },
      error: function() {
        // We need an error handler as well, to ensure another attempt gets scheduled
        if (!postComplete)
          setTimeout(ask, 500);
        }
      }

    });

  }

  $("#test").click(function() {

    // Since you only ever call post() once, you don't need a seperate function.
    // You can just put all the post() code here.

    var time = new Date().getTime();

    $.ajax({

      type: 'post',
      url: '/url/to/post' + '?time=' + time,
      data: {
        "some": "data"
      },

      success: function(data) {
        postComplete = true;
        alert(data);
      }
      error: function() {
        postComplete = true;
      }

    });

    if (!postComplete)
      setTimeout(ask, 500);
    }

  });

</script>

...虽然这仍然不能解决会话问题。

【讨论】:

  • 你说得对。我找到了函数 session_write_close() ,它似乎正在工作!我需要做的就是在操作开始时关闭会话并重新打开它只是为了更新我的会话变量。非常感谢您的建议。
  • 当心在同一个脚本中多次打开和关闭会话数据 - 我看到有人抱怨它不能可靠地工作,所以请确保彻底测试你想出的任何东西。跨度>
  • 嗯,也许更好的解决方案是将进度变量存储在数据库或文件中。我必须检查一下,谢谢!
【解决方案2】:

上面的@DaveRandom 正确指出您是会话存储锁的受害者。

解决方法非常简单。您想让处理post() 的脚本释放对会话数据的锁定,以便处理ask() 的脚本可以访问此会话数据。你可以通过session_write_close 做到这一点。

这里的细则是,在调用session_write_close 之后,您将无法访问会话变量,因此您需要相应地为post 构造脚本:

  1. $_SESSION 读取您需要的所有数据并保存一份副本。
  2. 调用session_write_close释放会话锁。
  3. 继续您的冗长操作。如果您需要会话数据,请从您自己的副本中获取,而不是直接通过 $_SESSION 获取。

或者,您可以在脚本的生命周期内多次切换会话锁定:

session_start();
$_SESSION['name'] = 'Jon';

// Quick operation that requires session data
echo 'Hello '.$_SESSION['name'];

//  Release session lock
session_write_close();

// Long operation that does not require session data.
sleep(10);

// Need access to session again
session_start();
echo 'Hello again '.$_SESSION['name'];

这种安排使得当脚本处于休眠状态时,其他脚本可以毫无问题地访问会话数据。

【讨论】:

  • 我差点说“在post 脚本中使用session_write_close()”,直到我重新阅读问题并意识到他想要做的是通过会话数据。这显然是你做不到的。当您需要更新进度信息时,您可以尝试session_write_close() 和随后的session_start() - 我自己从未尝试过,但我确实记得在这里看到一个问题,有人发现这不起作用,所以如果它在任何地方都有效它可能不会在任何地方工作。因此You will need to come up with another way of sharing data