Индикатор выполнения с PHP и Ajax

Я работаю над индикатором выполнения, который обновляет прогресс, используя аякс-запросы и переменные сеанса. Когда моя программа выполняет много времени, например, отправляет много писем и т. Д., Она просто устанавливает правильную переменную сеанса (которая содержит значение прогресса). Эта операция запускается функцией post () в коде ниже.

Тем временем вторая функция ask () выполняется в цикле каждые 500 мс. Он должен возвращать текущий прогресс в реальном времени. И вот проблема: каждый запрос, посланный ask (), ждет запроса, отправленного функцией post (). Смешная часть заключается в том, что если я установил какой-то URL, например google.com, а не url / to / progress, он отлично работает, за исключением того, что это не то, что я хочу :). Это означает, что проблема на стороне сервера.

Не уверен, что это важно, но я использую Yii Framework.

Весь код ниже – это только царапина (но работает), и единственная его цель – показать, что я имел в виду.

Заранее спасибо.

Извините за мой бедный английский 🙂

Просмотр части:

<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; } 

Я думаю, что ваша проблема здесь связана с сеансом.

Когда скрипт имеет открытый сеанс, он блокирует файл сеанса. Это означает, что любые последующие запросы, которые используют один и тот же идентификатор сеанса, будут помещены в очередь до тех пор, пока первый скрипт не выпустит его блокировку в файле сеанса. Вы можете заставить это с помощью session_write_close() – но это не поможет вам здесь, так как вы пытаетесь поделиться информацией о ходе с файлом сеанса, поэтому post скрипт должен будет сохранять данные сеанса открытыми и доступными для записи.

Вам нужно будет найти другой способ совместного использования данных между сценариями post и progress – если post имеет данные сеанса, открытые во время его выполнения, progress никогда не сможет получить доступ к данным сеанса до тех пор, пока post не завершит выполнение. Возможно, вы можете использовать идентификатор сеанса для создания временного файла, на который post имеет доступ на запись, в котором вы помещаете данные индикатора прогресса. progress может проверить файл и вернуть эти данные. Существует много вариантов IPC (межпроцессное общение) – это не особенно красиво, но у него есть преимущество максимальной переносимости.

В качестве побочного примечания – не передавайте строки в setInterval() , передавайте функции. Так что ваша строка должна действительно читать:

 var progress = setInterval(ask, 500); 

Но – было бы лучше использовать setTimeout() в обработчиках success / error функции ask() ajax. Это связано с тем, что с помощью 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> 

… хотя это еще не устраняет проблему сеанса.

@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']; 

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