Прогресс-бар с PHP и Ajax - PullRequest
       23

Прогресс-бар с PHP и Ajax

6 голосов
/ 16 февраля 2012

Я работаю над индикатором выполнения, который обновляет прогресс, используя запросы 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;
}

Ответы [ 2 ]

8 голосов
/ 16 февраля 2012

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

Когда у скрипта открыт сеанс, он блокирует файл сеанса. Это означает, что любые последующие запросы, которые используют тот же идентификатор сеанса, будут поставлены в очередь до тех пор, пока первый скрипт не освободит свою блокировку файла сеанса. Вы можете принудительно сделать это с помощью 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>

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

5 голосов
/ 16 февраля 2012

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

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

...