5

在 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”路径中。

任何帮助表示赞赏。提前致谢。

4

2 回答 2

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;

这将对作为参数传递给路由 ( /?file=myfile&file=otherfile) 的每个文件名运行 soffice。然后标准输出、标准错误和返回代码(应该是,我显然没有运行这个)作为 json 呈现给客户端(你可以很容易地记录它)。

于 2013-10-24T02:24:21.363 回答
3

使用 AnyEvent 创建单线程服务器

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->loop 而不是阻塞 CondVar:

EV->loop
于 2013-10-22T11:08:17.387 回答