在一个Mojolicious应用程序中,我试图在点击链接时将ODT文件转换为HTML。我使用shell命令“soffice”转换文件。转换文件需要一些时间。我向用户发送状态消息以通知他进度。我通过写入Mojo :: Log对象发送这些状态更新消息。然后,我在EventSource路由中订阅此日志对象。
然后我遍历文件并使用AnyEvent :: Util run_cmd执行外部“soffice”程序。
for my $file (@{ $filelist }) {
my $output_dir = './output_dir';
my $cmd = "soffice --headless --convert-to html --outdir '$output_dir' '$file'";
my $cv = AnyEvent->condvar;
my $w;
$w = run_cmd($cmd,
'>' => sub { my $out = shift;
&WriteToLog({ status => "cmd output '$out'..." });
undef $w;
$cv->send;
},
'2>' => sub { my $err = shift;
&WriteToLog({ status => "ERROR '$err'..." });
undef $w;
$cv->send;
}
);
$cv->recv;
}
从主要的AnyEvent教程中复制和粘贴。如果只有很少的文件要转换(大约2或3),那么一切顺利。通过EventSource连接发送的状态消息显示在客户端浏览器上。然后,在转换完所有文件后,将呈现网页。
如果要处理更多文件,则会转换一些文件,然后会出现线程标题中的错误消息。
包含上述代码的路由的路由是:
my $initdocs = $r->under->to('docroute#initdocs');
$initdocs->get('/showdocs')->to('docroute#showdocs');
上面的代码位于“initdocs”路径中。
感谢任何帮助。提前谢谢。
答案 0 :(得分:4)
我认为你要做的是调用soffice(阻塞)进程而不阻塞服务器线程的其余部分。我不是AE的专家,但我认为这不是run_cmd
的作用。它更接近于fork_call
所做的事情。也许你想做的事情更像是这样:
#!/usr/bin/env perl
use Mojolicious::Lite;
use EV;
use AnyEvent::Util 'fork_call';
any '/' => sub {
my $c = shift;
$c->render_later;
fork_call { `sleep 5 && echo 'hi'` } sub {
my $data = shift;
$c->render( text => $data );
};
};
app->start;
在我的示例中,我只是进行了一次简单的阻止呼叫,但您可以轻松地呼叫soffice
。
既然你说在返回客户端之前你可能需要转换几个不同的文件,你可能想要使用优秀的Mojo::IOLoop::Delay来管理这些过程。
#!/usr/bin/env perl
use Mojolicious::Lite;
use EV;
use AnyEvent::Util 'fork_call';
my @jobs = (
q{sleep 5 && echo 'hi'},
q{sleep 5 && echo 'bye'},
);
any '/' => sub {
my $c = shift;
$c->render_later;
my $delay = Mojo::IOLoop->delay;
$delay->on( finish => sub {
shift; $c->render(text => join '', @_ );
});
fork_call { `$_` } $delay->begin(0) for @jobs;
};
app->start;
再次,我只是捕获输出并将其发送到渲染调用,但请注意它在返回之前等待所有作业完成。更接近真实用例的可能是:
#!/usr/bin/env perl
use Mojolicious::Lite;
use EV;
use AnyEvent::Util 'fork_call';
use Capture::Tiny 'capture';
any '/' => sub {
my $c = shift;
my $files = $c->every_param('file');
$c->render_later;
my $delay = Mojo::IOLoop->delay;
$delay->on( finish => sub {
shift; $c->render( json => \@_ );
});
my $output_dir = './output_dir';
for my $file (@$files) {
my $cmd = "soffice --headless --convert-to html --outdir '$output_dir' '$file'";
fork_call { [ capture { system $cmd } ] } $delay->begin(0);
}
};
app->start;
这会将每个文件名作为param传递给路由(/?file=myfile&file=otherfile
)。然后stdout,stderr和返回代码(好吧应该是,我显然没有运行这个)作为json呈现给客户端(你可以轻松地记录它)。
答案 1 :(得分:3)
Creating single Thread Server with AnyEvent
如果您使用的是AnyEvent,通常需要处理CondVars。使用CondVar可以做两件事:要么注册将在CondVar被触发时调用的回调,要么调用recv并且它将阻塞直到触发CondVar。在Mojo :: Controller的路径中,您可能希望阻止,直到您获得要向用户显示的所有数据。
采用以下(组成)使用CondVar的示例:
未经测试的:
get '/' => sub {
...
my $cv = AnyEvent->condvar;
my $timer = AnyEvent->timer(after => 1, cb => sub { $cv->send(1) });
my $result = $cv->recv;
...
};
您将收到一条运行时错误,指出“AnyEvent :: CondVar:检测到递归阻塞等待”。也许这是因为Morbo还使用CondVar作为exit_guard运行无限长(在CondVar上阻塞是一种运行主循环的简单方法)。
我的方法是使用特定的事件循环,例如EV,并调用EV->循环而不是阻止CondVar:
EV->loop