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

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

person pawelo    schedule 16.02.2012    source источник
comment
какой ответ вы получаете от сервера? проверка инструментов разработчика ›сеть› запросы xhr   -  person Gabo Esquivel    schedule 16.02.2012


Ответы (2)


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

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

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

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

var progress = setInterval(ask, 500);

Но - лучше было бы использовать setTimeout() в _13 _ / _ 14_ обработчиках 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>

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

person DaveRandom    schedule 16.02.2012
comment
Вы были правы. Я нашел функцию session_write_close () и, похоже, она работает! Все, что мне нужно было сделать, это закрыть сеанс в начале действия и снова открыть его только для обновления моей переменной сеанса. Большое спасибо и спасибо за ваши советы. - person pawelo; 16.02.2012
comment
Остерегайтесь открывать и закрывать данные сеанса несколько раз в одном и том же скрипте - я видел, как люди жалуются, что он не работает надежно, поэтому обязательно тщательно проверяйте все, что вы делаете. - person DaveRandom; 16.02.2012
comment
Хммм, может быть, лучшим решением будет сохранить переменную прогресса в базе данных или файле. Я должен это проверить, спасибо! - person pawelo; 16.02.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'];

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

person Jon    schedule 16.02.2012
comment
Я почти сказал, что просто используйте session_write_close() в post скрипте, пока я не перечитал вопрос и не понял, что он пытается поделиться прогрессом длительной операции через данные сеанса. Что, очевидно, вы не можете сделать. Вы можете попробовать и session_write_close(), а затем session_start(), когда вам нужно обновить информацию о прогрессе - я никогда не пробовал это сам, но я помню, что видел здесь вопрос, когда кто-то обнаружил, что это не сработало, поэтому, если это сработает где угодно, это, вероятно, не сработает повсюду. Следовательно You will need to come up with another way of sharing data - person DaveRandom; 16.02.2012