Я пытаюсь сделать функцию синхронизации
#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use AE;
use Time::Piece;
use Promises backend =>['AnyEvent'], 'deferred';
use feature q(say);
use Data::Dumper;
$| = 1;
my $inputs = 'starting';
my $w2; $w2 = AE::timer 5, 0, sub {
say '***> AE::io timer : pass 5sec : ',
Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
say '***> making : inputs = next';
$inputs = 'next';
};
my $w3; $w3 = AE::timer 15, 15, sub {
say 'AE::io timer : passed 15 sec : ',
Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
};
sub delay {
my ($dl, $cb) = @_;
my $t;
$t = AE::timer $dl, 0, sub {
say '===> delay : delayed ', $dl, ' sec';
undef $t;
$cb->() if ref $cb eq 'CODE';
};
}
sub my_p_await{
my ($sec, $cond, $cb) = @_;
my $d = deferred;
say '===> my_p_await: staring...with ',$sec, ' sec: ',
Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
my $result = eval { $cond->() };
if (!$result) {
say '===> my_p_await: condition is false';
delay($sec, sub {
$cb->() if ref $cb eq 'CODE';
my_p_await($sec, $cond, $cb);
});
}else{
say '===> my_p_await: condition is true';
$d->resolve;
return;
}
return $d->promise;
}
deferred->resolve->promise->then( sub {
my_p_await(3, sub { $inputs eq 'next'},sub {say '0: cb..'} )
})
->then( sub {
say '';
say '****>==> Time: 0: ',
Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
});
AE::cv->recv;
и результат
===> my_p_await: staring...with 3 sec: 2024-08-26 18:55:13
===> my_p_await: condition is false
===> delay : delayed 3 sec
0: cb..
===> my_p_await: staring...with 3 sec: 2024-08-26 18:55:16
===> my_p_await: condition is false
***> AE::io timer : pass 5sec : 2024-08-26 18:55:18
***> making : inputs = next
===> delay : delayed 3 sec
0: cb..
===> my_p_await: staring...with 3 sec: 2024-08-26 18:55:19
===> my_p_await: condition is true
AE::io timer : passed 15 sec : 2024-08-26 18:55:28
Тогда он не пойдет дальше, поэтому я никогда не вижу сообщения «****>==> Time: 0:».
Даже таймер AE срабатывает через 15 секунд, и AnyEvent работает нормально.
Первый 5-секундный таймер срабатывает для строки $inputs, указывающей на «следующий».
поэтому условие my_p_await истинно.
Что я делаю неправильно?
Это все, что вам нужно:
sub delay {
my $sec = shift;
my $promise = deferred;
my $t;
$t = AE::timer $sec, 0, sub {
$t = undef;
log "delay: Delayed $sec s.";
$promise->resolve;
};
return $promise;
}
my $done = AE::cv;
delay( 5 )->then( sub {
log "then";
$done->send;
} );
$done->recv;
Объяснение проблемы
Прежде всего,
deferred->resolve->promise->then(sub {
my_p_await(3, sub { $inputs eq 'next' })
})
->then( sub {
...
});
это сложный способ письма
my_p_await(3, sub { $inputs eq 'next' })
->then(sub {
...
});
В обоих случаях код then
выполняется только после того, как обещание, возвращенное my_p_await
, будет выполнено. Если условие изначально ложно, в вашем коде этого никогда не произойдет.
Конечно, обещание, возвращенное другим вызовом my_p_await
, возвращает выполненное обещание. Но это другое обещание.
Итак, нам нужно убедиться, что обещание, возвращенное первоначальным вызовом my_p_wait
, выполнено.
Исправить
my_p_await
слишком сложен, и его исправление лишь добавит еще одну проблему. Устранение сложности – это способ исправить это. Использование сочетания обратных вызовов и обещаний «в случае успеха» делает my_p_await
сложным. Должен быть только один механизм успеха.
Сначала я посмотрю delay
. delay
не смешивает обратные вызовы и обещания «в случае успеха», но преобразование его в использование обещаний даст нам рекомендации по исправлению my_p_await
.
Обещание-возврат delay
:
sub delay {
my $sec = shift;
my $promise = deferred;
log "delay: Delaying $sec s.";
my $t;
$t = AE::timer $sec, 0, sub {
$t = undef;
log "delay: Delayed $sec s.";
$promise->resolve();
};
return $promise;
}
Одна загвоздка: если бы это вызвало исключение, все пошло бы совсем не так. Спецификация Promise/A+ Promises(.pm), как утверждается, обеспечивает решение этой проблемы, но Promises(.pm) его не реализовали. Итак, мы собираемся реализовать это:
# Promises(.pm) doesn't follow the Promise/A+ spec as it claims.
# `promise` is a replacement for `deferred` the follows the spec.
sub promise {
my $cb = shift; # Optional.
my $promise = Promises::deferred;
if ( $cb ) {
eval {
$cb->(
sub { $promise->resolve( @_ ) },
sub { $promise->reject( @_ ) },
);
};
$promise->reject( $@ ) if $@;
}
return $promise;
}
Используя это, мы можем написать сейф delay
.
sub delay {
my $sec = shift;
return promise sub {
my $resolve = shift;
log "delay: Delaying $sec s.";
my $t;
$t = AE::timer $sec, 0, sub {
$t = undef;
log "delay: Delayed $sec s.";
$resolve->();
};
};
}
Вероятно, нам не нужно было этого делать, поскольку не должно быть исключено никаких исключений, но делать такие предположения — плохая практика.
Теперь у нас есть представление о том, как должен выглядеть my_p_wait
. Давайте удалим обратный вызов «в случае успеха» из my_p_wait
(и переименуем его).
sub polling_cond_wait {
my ( $sec, $cond ) = @_;
return promise sub {
my $resolve = shift;
log "polling_cond_wait: Start";
my $result = eval { $cond->() };
if ( $result ) {
log "polling_cond_wait: Condition is true";
$resolve->();
} else {
log "polling_cond_wait: Condition is false";
delay( $sec )->then( sub {
polling_cond_wait( $sec, $cond )->then( sub {
$resolve->();
} );
} );
}
};
}
Остается только основной код.
use strict;
use warnings;
use feature qw( say );
use AE;
use AnyEvent;
use Promises backend => [ 'AnyEvent' ];
use Time::Piece qw( localtime );
$| = 1;
sub log {
say localtime->strftime( "[%Y-%m-%d %H:%M:%S] " ), @_;
}
# ...
my $done = AE::cv;
my $inputs = 'starting';
{
my $t; $t = AE::timer 5, 0, sub {
$t = undef;
log "5 s timer completed. Making `$inputs` next.";
$inputs = 'next';
};
}
polling_cond_wait( 3, sub { $inputs eq 'next' } )->then( sub {
log "then";
$done->send;
} );
$done->recv;
Выход:
[2024-08-26 12:38:53] polling_cond_wait: Starting.
[2024-08-26 12:38:53] polling_cond_wait: Condition is false.
[2024-08-26 12:38:53] delay: Delaying 3 s.
[2024-08-26 12:38:56] delay: Delayed 3 s.
[2024-08-26 12:38:56] polling_cond_wait: Starting.
[2024-08-26 12:38:56] polling_cond_wait: Condition is false.
[2024-08-26 12:38:56] delay: Delaying 3 s.
[2024-08-26 12:38:58] 5 s timer completed. Making `starting` next.
[2024-08-26 12:38:59] delay: Delayed 3 s.
[2024-08-26 12:38:59] polling_cond_wait: Starting.
[2024-08-26 12:38:59] polling_cond_wait: Condition is true.
[2024-08-26 12:38:59] then
Лучшие альтернативы
Здесь нет смысла проводить опрос. Ниже приведено лучшее решение:
my $done = AE::cv;
my $promise = promise;
{
my $t; $t = AE::timer 5, 0, sub {
$t = undef;
log "5 s timer completed. Resolving promise.";
$promise->resolve();
};
}
$promise->then( sub {
log "then";
$done->send;
} );
$done->recv;
И это упрощается до следующего:
my $done = AE::cv;
delay( 5 )->then( sub {
say ts(), " then";
$done->send;
} );
$done->recv;
О боже, большое спасибо, икегами, думаю, мне нужно много времени, чтобы переварить приведенный выше код :(, я рад видеть работающий код :)