Избегание состояния гонки javascript
-
19-08-2019 - |
Вопрос
Вот такой сценарий:
Моим пользователям представлена таблица, по сути, урезанная версия электронной таблицы.В каждой строке таблицы есть текстовые поля.Когда они меняют значение в текстовом поле, я выполняю проверку их входных данных, обновляю коллекцию, которая управляет сеткой, и перерисовываю промежуточные итоги на странице.Все это обрабатывается событием onChange каждого текстового поля.
Когда они нажимают кнопку "Сохранить", я использую событие OnClick кнопки для выполнения некоторой окончательной проверки сумм, а затем отправляю весь их ввод в веб-службу, сохраняя его.
По крайней мере, это то, что происходит, если они переходят через форму к кнопке "Отправить".
Проблема в том, что если они вводят значение, а затем сразу нажимают кнопку сохранить, SaveForm() начинает выполняться до завершения UserInputChanged() - условие гонки.Мой код не использует setTimeout, но я использую его для имитации вялого кода проверки UserInputChanged:
<!-- snip -->
<script>
var amount = null;
var currentControl = null;
function UserInputChanged(control) {
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
function SaveForm() {
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
}
</script>
<!-- snip -->
Amount: <input type="text" id="UserInputValue" onchange="UserInputChanged(this);" /> <br />
Subtotal: <span id="Subtotal"></span> <br />
<input type="button" onclick="SaveForm();" value="Save" /> <br /><br />
Saved amount: <span id="SavedAmount"></span>
<!-- snip -->
Я не думаю, что смогу ускорить код проверки - он довольно легкий, но, по-видимому, достаточно медленный, чтобы код пытался вызвать веб-службу до завершения проверки.
На моей машине ~ 95 мс - это магическое число между выполнением кода проверки до начала сохранения кода.Это значение может быть выше или ниже в зависимости от скорости работы компьютера пользователя.
У кого-нибудь есть какие-нибудь идеи, как справиться с этим состоянием?Коллега предложил использовать семафор во время выполнения кода проверки и цикл занятости в коде сохранения, чтобы дождаться разблокировки семафора, но я бы хотел избежать использования какого-либо цикла занятости в моем коде.
Решение
Используйте семафор (давайте назовем его StillNeedsValidating).если функция SaveForm видит, что семафор StillNeedsValidating активирован, попросите ее активировать второй собственный семафор (который я буду называть здесь FormNeedsSaving) и вернуть.Когда функция проверки завершается, если семафор FormNeedsSaving включен, она вызывает функцию SaveForm самостоятельно.
В jankcode;
function UserInputChanged(control) {
StillNeedsValidating = true;
// do validation
StillNeedsValidating = false;
if (FormNeedsSaving) saveForm();
}
function SaveForm() {
if (StillNeedsValidating) { FormNeedsSaving=true; return; }
// call web service to save value
FormNeedsSaving = false;
}
Другие советы
Отключите кнопку сохранить во время проверки.Установите для него значение отключено, как первое, что делает проверка, и повторно включите его по завершении.
например ,
function UserInputChanged(control) {
// --> disable button here --<
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
и
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
// --> enable button here if validation passes --<
}
Вам придется подстроиться, когда вы удалите setTimeout и сделаете проверку одной функцией, но если у ваших пользователей нет сверхчеловеческих рефлексов, все должно быть в порядке.
Я думаю, что тайм-аут является причиной вашей проблемы...если это будет простой код (без асинхронных вызовов AJAX, тайм-аутов и т.д.), То я не думаю, что SaveForm будет выполнен до завершения UserInputChanged.
Семафор или мьютекс, вероятно, лучший способ, но вместо цикла занятости просто используйте setTimeout()
чтобы имитировать спящий режим потока.Вот так:
busy = false;
function UserInputChanged(control) {
busy = true;
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
function SaveForm() {
if(busy)
{
setTimeout("SaveForm()", 10);
return;
}
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
busy = false;
}
Вы могли бы настроить повторяющуюся функцию, которая отслеживает состояние всей сетки и вызывает событие, указывающее, является ли вся сетка допустимой или нет.
Затем ваша кнопка "отправить форму" будет сама включаться или отключаться в зависимости от этого статуса.
О, сейчас я вижу похожий ответ - это, конечно, тоже работает.
При работе с асинхронными источниками данных у вас, безусловно, могут возникнуть условия гонки, потому что поток процесса JavaScript продолжает выполнять директивы, которые могут зависеть от данных, которые еще не были возвращены из удаленного источника данных.Вот почему у нас есть функции обратного вызова.
В вашем примере вызов кода проверки должен иметь функцию обратного вызова, которая может что-то сделать, когда проверка вернется.
Однако, создавая что-то со сложной логикой или пытаясь устранить неполадки или улучшить существующую серию обратных вызовов, вы можете сойти с ума.
Именно по этой причине я создал библиотеку proto-q: http://code.google.com/p/proto-q/
Проверьте это, если вы часто выполняете подобную работу.
У вас нет условия гонки, условия гонки не могут выполняться в javascript, поскольку javascript является однопоточным, поэтому 2 потока не могут мешать друг другу.
Пример, который вы приводите, не очень хороший.Вызов setTimeout поместит вызываемую функцию в очередь в движке javascript и запустит ее позже.Если в этот момент вы нажмете кнопку сохранить, функция setTimeout не будет вызвана до тех пор, пока сохранение не будет полностью завершено.
Что, вероятно, происходит в вашем javascript, так это то, что событие onClick вызывается движком javascript перед вызовом события onChange.
В качестве подсказки имейте в виду, что javascript является однопоточным, если только вы не используете отладчик javascript (firebug, Microsoft screipt debugger).Эти программы перехватывают поток и приостанавливают его.С этого момента могут запускаться другие потоки (либо с помощью событий, вызовов setTimeout, либо обработчиков XMLHttp), из-за чего создается впечатление, что javascript может запускать несколько потоков одновременно.