Я создал задание с циклом foreach, которое отправляет другое задание. Есть ли способ запустить даже после завершения всех вложенных заданий?
При срабатывании вот что происходит
Шаг 1. Сначала я запускаю пакетное задание
GenerateBatchReports::dispatch($orderable);
Шаг 2. Затем мы запускаем цикл и ставим в очередь другие задания.
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$dir = storage_path('reports/tmp/'.str_slug($this->event->company) . '-event');
if (file_exists($dir)) {
File::deleteDirectory($dir);
}
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
GenerateSingleReport::dispatch($model);
}
}
Мне просто нужно знать, когда все вложенные задания будут выполнены, чтобы я мог заархивировать отчеты и отправить их пользователю по электронной почте. Когда пакетное задание ставит в очередь все вложенные задания, оно удаляется из списка. Есть ли способ сохранить задание до тех пор, пока не будут выполнены вложенные задания, а затем запустить событие?
Любая помощь будет оценена по достоинству.
Спасибо. Это выглядит многообещающе, но мое приложение все еще использует laravel 5.5. Похоже, что метод DispatchNow не был представлен до версии 5.7.
В этом случае вы можете использовать sync. Проверьте нижнюю половину моего ответа.
Найдите цепочку заданий в документах очереди Laravel. Это решит вашу проблему, но ваши задания не будут выполняться параллельно (то же самое произойдет при использовании синхронизации)
Что говорит против запуска пользовательского события в конце последней работы?






Для ларавела >= 5.7
Вы можете использовать метод dispatchNow. Это сохранит родительское задание, пока обрабатываются дочерние задания:
https://laravel.com/docs/5.8/queues#synchronous-dispatching
Родительская работа:
public function handle()
{
// ...
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
GenerateSingleReport::dispatchNow($model);
}
// then do something else...
}
Для ларавела 5.2 - 5.6
Вы можете использовать соединение sync:
https://laravel.com/docs/5.5/queues#настройка-очереди-и-соединения
Убедитесь, что соединение определено в вашем config/queue.php:
https://github.com/laravel/laravel/blob/5.5/config/queue.php#L31
Родительское задание (ПРИМЕЧАНИЕ. Этот синтаксис предназначен для версии 5.5. Документация для версии 5.2 немного отличается):
public function handle()
{
// ...
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
GenerateSingleReport::dispatch($model)->onConnection('sync');
}
// then do something else...
}
Использование sync противоречит идее использования очереди для обработки заданий в фоновом режиме. Никогда не делайте этого в производственной среде.
@Namoshek Родительское задание все еще находится в асинхронной очереди, поэтому синхронизированные дочерние задания все равно будут выполняться вне основного цикла запроса.
Это интересно, надо попробовать. Думаю, должна быть возможность построить что-то очень похожее с событиями (одно отправленное фоновое задание + события с обработчиками без очереди, где каждый обработчик запускает следующий обработчик через событие).
Вы можете использовать Laravel цепочка заданий. Это позволяет вам запускать несколько заданий последовательно, и если одно из них выйдет из строя, остальные в цепочке не будут запущены.
Основной синтаксис выглядит следующим образом:
FirstJob::withChain([
new SecondJob($param),
new ThirdJob($param)
])->dispatch($param_for_first_job);
В вашем случае вы можете добавить все свои задания GenerateSingleReport в массив, кроме первого, и добавить, а затем добавить последнее задание, которое вы хотите запустить, в конец массива. Затем вы можете передать этот массив методу withChain в первом задании.
$jobs = [];
$first_job = null;
$first_parameter = null;
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
if (empty($first_job)) {
$first_job = GenerateSingleReport;
$first_parameter = $model;
} else {
$jobs[] = new GenerateSingleReport($model);
}
}
$jobs[] = new FinalJob();
$first_job->withChain($jobs)->dispatch($first_parameter);
Обновлять: Laravel 8 (выпуск запланирован на 8 сентября 2020 г.) обеспечит пакетную обработку заданий. Эта функция уже задокументировано, вероятно, идеально подходит для сценария вложенных заданий и выглядит так:
$batch = Bus::batch([
new ProcessPodcast(Podcast::find(1)),
new ProcessPodcast(Podcast::find(2)),
new ProcessPodcast(Podcast::find(3)),
new ProcessPodcast(Podcast::find(4)),
new ProcessPodcast(Podcast::find(5)),
])->then(function (Batch $batch) {
// All jobs completed successfully...
})->catch(function (Batch $batch, Throwable $e) {
// First batch job failure detected...
})->finally(function (Batch $batch) {
// The batch has finished executing...
})->dispatch();
Мы также сможем добавлять дополнительные пакетные задания на лету:
$this->batch()->add(Collection::times(1000, function () {
return new ImportContacts;
}));
Я придумал другое решение, потому что у меня есть очередь, использующая несколько процессов. Итак, для меня:
dispatchNow, потому что я хочу, чтобы задания выполнялись параллельно.Таким образом, мое не элегантное решение, удовлетворяющее требованиям, состоит в том, чтобы отправить все вложенные задания и, в последнем случае, отправить окончательное задание с задержкой в пару секунд, чтобы убедиться, что все другие вложенные задания, которые могут все еще выполняться параллельно, будут прекращено.
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$last_participant_id = $this->event->participants->last()->id;
foreach($this->event->participants as $participant) {
$is_last = $participant->id === $last_participant_id;
GenerateSingleReport::dispatch($model, $is_last);
}
}
и в GenerateSingleReport.php
class GenerateSingleReport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $model;
protected $runFinalJob;
public function __construct($model, $run_final_job = false)
{
$this->model = $model;
$this->runFinalJob = $run_final_job;
}
public function handle()
{
// job normal stuff…
if ($this->runFinalJob) {
FinalJob::dispatch()->delay(30);
}
}
}
Подкидываю другую идею, значит код не безупречен. Возможно, можно создать задание-оболочку, предназначенное для запуска последнего вложенного задания, связанного с последним заданием.
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$last_participant_id = $this->event->participants->last()->id;
foreach($this->event->participants as $participant) {
$is_last = $participant->id === $last_participant_id;
if ($is_last) {
ChainWithDelay::dispatch(
new GenerateSingleReport($model), // last nested job
new FinalJob(), // final job
30 // delay
);
} else {
GenerateSingleReport::dispatch($model, $is_last);
}
}
}
И в ChainWithDelay.php
class ChainWithDelay implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $job;
protected $finalJob;
protected $delay;
public function __construct($job, $final_job, $delay = 0)
{
$this->job = $job;
$this->finalJob = $final_job;
$this->delay = $delay;
}
public function handle()
{
$this->job
->withChain($this->finalJob->delay($this->delay))
->dispatchNow();
}
}
Вы можете запускать дочерние задания синхронно, что будет поддерживать родительское задание до тех пор, пока они все не будут выполнены: laravel.com/docs/5.8/очереди#синхронная-диспетчеризация