3

我们已经有一个使用 AnyEvent 的库。它在内部使用 AnyEvent 并最终返回一个值(同步- 而不是回调)。有什么办法可以将这个库与 Mojolicious 一起使用吗?

它做了类似的事情:

#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use Mojolicious::Lite;

# To the caller, getData is a synchronous sub that returns a value.
# The fact that it uses AnyEvent is an internal implementation detail of
# getData
sub getData {
    my $cv = AnyEvent->condvar;

    my $w = AnyEvent->timer (after => 5, cb => sub {
        # Perform many async operations, represented here by a single timer,
        # calculating a final result that is sent:
        $cv->send(42);
    });

    my $result = $cv->recv;
    # postProcess($result);
    return $result;
}

get '/' => sub {
    my ($c) = @_;
    $c->render(text => "Data is: " . getData());
};

app->start;

当我同时运行morbo app.pl并尝试get '/'从两个浏览器选项卡中运行时,我收到此错误:

AnyEvent::CondVar: recursive blocking wait attempted at /bla/bla/app.pl line 16.

我认为正在发生的事情是 morbo 在内部使用 EV ,因此当它调度处理 firstget '/'时,$cv->recv最终被调用,返回到 EV 事件循环。EV 现在尝试处理第二个get '/'$cv->resv再次被调用,从而触发错误。

我知道我可以重构$cvout ofgetData()以制作异步版本,但实际上真正的“getData”在许多地方被调用,并且将所有对“getData”的调用转换为异步代码是不可行的。

所以我的问题是:有什么方法可以getData()在使用morbo/Mojolicious 时可靠地调用上面的内容?我想get '/'阻止,直到它完成。

编辑: AnyEvent 的在模块中做什么部分明确地说:

永远不要在条件变量上调用 ->recv,除非你知道已经调用了 ->send 方法。这是因为它会停止整个程序,而使用事件的全部意义在于保持交互。

getData()以上违反了这一点。现在我明白了 AnyEvent 文档那部分的原因:-)

4

1 回答 1

3

您可以通过确保 Mojolicious 和 AnyEvent 不使用相同的主循环来避免此问题,方法是设置 env varMOJO_REACTOR=Mojo::Reactor::Poll或在其他任何内容之前使用AnyEvent::Loop以便 AnyEvent 使用其纯 perl 循环(首选,因为它不是被用作主循环)。不幸的是,没有办法让它使用单独实例化的循环,例如Mojo::UserAgent 阻塞请求的功能。

请注意,通常,您希望多个循环使用者共享主循环;这是一个奇怪的情况,您希望内部消费者阻止。


一个更“异步”的长期解决方案可能是简单地允许操作共享主循环,因为 Mojolicious 是为非阻塞响应操作而设计的。您可以通过首先确保两者确实共享 EV 主循环(例如通过设置MOJO_REACTOR=Mojo::Reactor::EV),然后更改您的函数或创建该函数的新版本来返回一个承诺,即该函数将异步填充结果,并将依赖于该结果的任何进一步的功能链接到该承诺之外。当然,您的函数比您在此处使用的单个计时器更复杂,但它仍然值得考虑 - 它允许应用程序在等待 AnyEvent 操作的结果时继续服务其他请求。

sub getData_p {
  my $p = Mojo::Promise->new;
  my $w; # keep a strong reference to $w for AnyEvent's reasons
  $w = AnyEvent->timer(after => 5, cb => sub { $p->done(42); undef $w });
  return $p;
}

get '/' => sub {
  my ($c) = @_;
  my $tx = $c->render_later->tx; # keep strong reference to $tx
  getData_p()->then(sub { $c->render(text => "Data is: $_[0]") })
    ->catch(sub { $c->reply->exception($_[0]); undef $tx });
};
于 2019-06-05T16:09:44.950 回答