【问题标题】:Running a background task using Symfony Process without having to wait for the process to finish使用 Symfony 进程运行后台任务,而无需等待进程完成
【发布时间】:2016-06-09 18:41:03
【问题描述】:

用户提交表单后,我想渲染一个视图文件,然后我想启动一个后台任务来处理五个 MS Excel 文件(每个可能有多达 2000 行),但方式是这样用户不必等待进程完成即可查看页面。任务完成后我会通过邮件通知用户。

我正在使用 Symfony Framework 3。我在下面包含了我的代码。它没有做我想要实现的目标。提交表单后,页面仅在整个后台任务完成时加载。

我不确定,但在谷歌上搜索了很多之后,我认为The kernel.terminate Event 可能在这里有用。但我似乎无法理解如何使用它。

你能告诉我如何解决这个问题吗?

这是我的代码:

我已经创建了一个控制台命令:

class GreetCommand extends ContainerAwareCommand {

  protected function configure()
  {
    $this->setName('hello:world');
  }

  protected function execute(InputInterface $input, OutputInterface $output)
  {
   // My code to execute
  }

}

在我的控制器中我有

$process = new Process('ls -lsa');
$process->disableOutput();
$that = $this;

$process->start(function ($type, $buffer) use ($that) {
        $command = new GreetCommand();
        $command->setContainer($this->container);
        $input = new ArrayInput(array());
        $output = new NullOutput;
        $resultCode = $command->run($input, $output);

 });

 return $this->render('success.html.php',  array('msg' => 'Registraion Successful'));

更新

我已经使用 PHP 的连接处理功能解决了这个问题。

感谢post

【问题讨论】:

  • 用一些关于连接处理功能实现的信息来更新你的帖子会很有用!
  • @mpilliador - 您可以简单地在 kernel.terminate 上创建一个事件侦听器或异步运行进程(请参阅下面的 Denis Alimov 回答)。

标签: php symfony


【解决方案1】:

简单使用 Symfony 进程选项

create_new_console

来自 Symfony 进程 Source

 /**
  * Defines options to pass to the underlying proc_open().
  *
  * @see https://php.net/proc_open for the options supported by PHP.
  *
  * Enabling the "create_new_console" option allows a subprocess to continue
  * to run after the main process exited, on both Windows and *nix
  */
  public function setOptions(array $options)

那么让我们这样做吧:

$process = new Process($cmds);
$process->setOptions(['create_new_console' => true]);
$process->start();

【讨论】:

  • 在 Windows 上,它会启动命令提示符。有什么办法可以避免打开命令提示符。
  • 我找不到 PHP 的方法。无论如何,即使您手动关闭提示,您的进程仍将继续运行。所以也许可以通过分析windows taskmanager并在shell中调用一些特定的windows task命令。看看superuser.com/a/1189987
【解决方案2】:

异步运行进程

你也可以启动子进程,然后让它异步运行, 每当您检索主进程中的输出和状态 需要它。使用 start() 方法启动一个异步进程

documentation

所以,要异步启动命令,您应该使用命令创建新进程并启动它

$process = new Process('php bin/console hello:word');
$process->start();

考虑将其更改为完整路径,例如 \usr\bin\php \var\www\html\bin\console hello:word

还有一个很好的捆绑包cocur/background-process 你可以使用它,或者至少阅读文档以了解它是如何工作的。

【讨论】:

  • 非常感谢您的回答。我已经尝试了上面的代码,但它仍在等待进程完成然后渲染页面。你认为这是因为我在 Windows 上,如果我迁移到 Linux 可能会起作用吗?
  • @black_belt 是的,这可能是造成这种情况的原因。
  • @black_belt,没有。这不是因为你在窗户上。您的主进程是启动该进程的应用程序。子进程的输出流附加到主进程,它将阻止应用进程终止,直到子进程完成。
  • 这个答案听起来不对。它在文档中说:“如果在子进程有机会完成之前发送了响应,则服务器进程将被终止(取决于您的操作系统)。”这与问题要求的相反。
  • @Alexander Rechsteiner 您正在查看 Symfony 4 文档。我猜 2 年前我们谈论的是 Symfony 2。
【解决方案3】:

在控制器中使用:

use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;

$myVar = new MyObject();
$this->get('event_dispatcher')->addListener(KernelEvents::TERMINATE, function(PostResponseEvent $event) use($myVar) {
    //You logic here
    $request = $event->getRequest();
    $test = $myVar->getMyStuff();
});

但这不是一个好习惯,请阅读正常的registering event listeners

kernel.terminate 事件将在向用户发送响应后调度。

【讨论】:

    【解决方案4】:

    我玩游戏有点晚了,但我刚刚使用fromShellCommandLine() method找到了解决此问题的方法:

    use Symfony\Component\Process\Process;
    
    Process::fromShellCommandline('/usr/bin/php /var/www/bin/console hello:world')->start();
    

    这样可以异步启动新进程/运行命令。

    【讨论】:

    • 请注意,这仅适用于 4.3+。
    【解决方案5】:

    有一个名为 AsyncServiceCallBundle 的包,它允许您在后台调用服务的方法。

    您可以参考this 答案以了解有关内部如何完成的更多详细信息。您需要做的就是调用服务的方法,如下所示:

    $this->get('krlove.async')->call('service_id', 'method', [$arg1, $arg2]);
    

    【讨论】:

      【解决方案6】:

      我猜你想要的是将必要的数据存储在数据库中,然后让 cronjob/queue 执行实际命令,而不是尝试直接从控制器执行它。

      在 /etc/crontab 中添加如下内容,让它每 5 分钟运行一次命令

      */5 * * * * root /usr/bin/php /path/to/bin/console hello:world
      

      然后,让您的命令查询数据库中存储的数据并让它处理 excel 文件

      【讨论】:

        【解决方案7】:

        即使已经回答,
        如果需要,我会在此处发布我的工作解决方案。
        在我的控制器中

        public function exportDossiersAction(Request $request)
        {    
            $form = $this->createForm(ExportType::class, null, []);
            $form->handleRequest($request);
        
            if ($form->isSubmitted() && $form->isValid()) {
                $filters = ["status" => $form->get('status')->getData()];
        
                $phpBinaryFinder = new PhpExecutableFinder();
                $phpBinaryPath = $phpBinaryFinder->find();
                $projectRoot = $this->get('kernel')->getProjectDir();
        
                $process = new Process([$phpBinaryPath, $projectRoot . '/bin/console', 'myapp:files:export', json_encode($filters)]);
                $process->setTimeout(36000); //10min
        
                // let the process run in the background
                $this->get('event_dispatcher')->addListener(KernelEvents::TERMINATE, function() use($process) {
                    $process->start();
                    $process->wait();
                });
        
               // render this messsage in twig
                $this->addFlash("success", "The export files is initiated");
        
                return $this->redirectToRoute('home');
             }
        
           return $this->render('$PATH/export.html.twig', [
                'form' => $form->createView()
            ]);
        }
        

        【讨论】:

          猜你喜欢
          • 2011-03-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-12
          • 1970-01-01
          • 2010-11-06
          相关资源
          最近更新 更多