我正在使用Mojolicious非阻塞方法(Promise)从外部系统请求数据。 1)我想立即通知用户该过程已经开始; 2)我想扩展这个程序。
下面的代码适用于少量数字(几百个),具有更多数字,我会收到一个错误[error] Can't create pipe: Too many open files at /path/lib/perl5/Mojo/IOLoop.pm line 156.
问题1 )如何限制我承诺的数量产生(以下代码中的map
)
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mojolicious::Plugin::TtRenderer;
sub isPrime
{
my ($n) = @_;
my $e = sqrt($n);
for (my $i=2; $i<$e; $i++) {
return 0 if $n%$i==0;
}
return 1;
}
sub makeApromise
{
my ($number) = @_;
my $promise = Mojo::Promise->new;
Mojo::IOLoop->subprocess(
sub { # first callback is executed in subprocess
my %response;
# Simulate a long computational process
$response{'number'} = $number;
$response{'isPrime'} = isPrime($number);
return \%response;
},
sub { # second callback resolves promise with subprocess result
my ($self, $err, @result) = @_;
return $promise->reject($err) if $err;
$promise->resolve(@result);
},
);
return $promise;
}
plugin 'tt_renderer'; # automatically render *.html.tt templates
any '/' => sub {
my ($self) = @_;
my $lines = $self->param( 'textarea' );
if ($lines) {
my @numbers;
foreach my $number (split(/\r?\n/, $lines)) {
push(@numbers, $number) if $number =~ /^\d+$/;
}
if (@numbers) {
####################################
### This is the problem below... ###
my @promises = map { makeApromise($_) } @numbers;
####################################
# MojoPromise Wait
Mojo::Promise->all(@promises)
->then(sub {
my @values = map { $_->[0] } @_;
foreach my $response (@values) {
#print STDERR $response->{'number'}, " => ", $response->{'isPrime'}, "\n";
# Prepare email...
}
# Send an email...
})
#->wait # Don't wait? I want to tell the user to wait for an email as quickly as possible...
if @promises;
}
$self->stash(done => "1",);
}
$self->render(template => 'index', format => 'html', handler => 'tt');
};
app->start;
__DATA__
@@ index.html.tt
<!DOCTYPE html>
<html lang="en">
<head>
<title>Make A Promise</title>
</head>
<body>
[% IF done %]
<h3>Thank you! You will receive an email shortly with the results.</h3>
[% ELSE %]
<h3>Enter numbers...</h3>
<form role="form" action="/" method="post">
<textarea name="textarea" rows="5" autofocus required></textarea>
<button type="submit">Submit</button>
</form>
[% END %]
</body>
</html>
我注释了wait
;但是,似乎代码仍在阻塞。 问题2 )如何立即通知用户该过程已经开始? (即当我stash
done
变量时)
答案 0 :(得分:1)
问题不是承诺的数量,而是子流程的数量。一种限制方法是简单地限制一次在程序逻辑中创建的数量。设置一个限制并从@numbers中检索很多(也许使用splice),然后产生这些子过程,而不是一次在地图中产生它们。创建一个-> allpromise并等待它们,然后在该Promise上附加-> then以检索下一个数字,依此类推。
另一种选择是使用Future::Utils fmap_concat,它可以通过提供一些最大未偿还期货来处理限速代码。您的诺言兑现函数可以应用Mojo::Promise::Role::Futurify来链接后面的Future以这种方式使用。
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mojo::File 'path';
use Mojo::IOLoop;
use Mojo::Promise;
use Future::Utils 'fmap_concat';
get '/' => sub {
my $c = shift;
my $count = $c->param('count') // 0;
my @numbers = 1..$count;
if (@numbers) {
my $result_f = fmap_concat {
my $number = shift;
my $p = Mojo::Promise->new;
Mojo::IOLoop->subprocess(sub {
sleep 2;
return $number+1;
}, sub {
my ($subprocess, $err, @result) = @_;
return $p->reject($err) if $err;
$p->resolve(@result);
});
return $p->with_roles('Mojo::Promise::Role::Futurify')->futurify;
} foreach => \@numbers, concurrent => 20;
$result_f->on_done(sub {
my @values = @_;
foreach my $response (@values) {
$c->app->log->info($response);
}
})->on_fail(sub {
my $error = shift;
$c->app->log->fatal($error);
})->retain;
$c->stash(done => 1);
}
$c->render(text => "Processing $count numbers\n");
};
app->start;
对于wait方法,如果事件循环已在运行,则此操作将不执行任何操作(如果您在Mojolicious守护程序中启动应用程序,则该事件将在webapp响应处理程序中运行)不支持异步响应)。设置子流程后,将立即运行回调之外的-> stash和-> render调用。然后响应处理程序将完成,并且事件循环将再次获得控制,这将在Promise解决后触发相应的-> then回调。渲染不应等待设置子流程之外的任何事情;由于您说的可能有数百个,这可能是您遇到的速度下降。确保您使用的是Mojolicious 7.86或更高版本,因为Subprocess已更改,因此在事件循环的下一个刻度(响应处理程序完成之后)才会发生派生。
我还将注意到,子流程并不是真正为此设计的;它们是为执行缓慢的代码而设计的,该代码仍会在响应中将最终结果返回给浏览器(Mojolicious::Plugin::Subprocess对于此用例而言非常有用)。我可以看到的一个问题是,如果重新启动应用程序,则任何仍在等待处理的子进程都将被忽略。对于您想起而忘却的工作,您可以考虑像Minion这样的工作队列,它可以很好地集成到Mojolicious应用中,并通过单独的工作进程运行。