SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

لماذا لا تعمل مهمة cron؟ 5 أسباب شائعة

اكتشف خمسة أسباب لفشل مهمة cron: PATH محدود، علامة % غير مهروبة، ملف crontab خاطئ، مخرجات تصل إلى البريد، ونص يفترض shell الخاص بك.

سبب عدم تشغيل مهمة cron

مهمة cron التي «لا تعمل مطلقاً» تكون قد عملت في الغالب. لقد عملت في بيئة لا تطابق shell الخاص بك، ثم فشلت خلال الثانية الأولى، وأُرسلت الرسالة إلى مكان لا تقرؤه. تفسّر خمسة أسباب معظم هذه الحالات: مسار البحث، وعلامة النسبة المئوية، وملف crontab غير الصحيح، والمخرجات التي أُرسلت بالبريد، والبرنامج النصي الذي يتوقع جلسة تسجيل دخول.

cron هو daemon (خدمة تعمل في الخلفية) يقرأ ملفات crontab ويبدأ الأوامر وفق جدول زمني. لا يقرأ .bashrc، ولا يفتح terminal، ولا يبدأ login shell، ولا يخبرك عند فشل أحد الأوامر. تنشأ كل حالة أدناه من هذه الحقائق الأربع.

تعامل مع هذه الأسباب بالترتيب، وابدأ بالسؤال الذي يسبقها جميعاً: هل نفّذ cron المهمة أصلاً؟ تختلف حالة «لم يبدأ cron المهمة» عن حالة «بدأت المهمة ثم توقفت»، ولا يوجد بين المشكلتين شيء مشترك، لذلك أجب عن هذا السؤال أولاً.

هل عمل cron أصلاً؟

يختلف اسم الوحدة بحسب عائلة التوزيعة. تحقّق من الاسمين، ثم اقرأ السجل.

systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"

تسمّي Debian وUbuntu الوحدة cron. وتسمّيها Fedora وRocky وAlma crond. يوجد اسم واحد فقط من هذين الاسمين على الجهاز، لذلك من الطبيعي أن يعرض أحد الأمرين رسالة تفيد بأن الوحدة غير معروفة، ولا يعني ذلك وجود خلل.

اقرأ الإدخالات التي كتبها نظامك نفسه. لا تبحث عن سطر منسوخ من دليل، لأن الصياغة تختلف بين تطبيقات cron وإعدادات التسجيل. أنت تتحقق من أمرين فقط: هل يوجد إدخال في الدقيقة التي يحددها الجدول، وهل يذكر هذا الإدخال أمرك. إذا ذكر الإدخال أمرك، فهذا يعني أن cron نفّذ ما عليه وأن الفشل داخل الأمر. أما عدم وجود أي إدخال، فيعني أن cron لم يتلقَّ جدولك، وهذا هو السبب 3 أدناه.

ترسل بعض الصور رسائل cron عبر rsyslog إلى ملف بدلاً من journal. ابحث في /var/log عن ملف يحمل اسم cron أو syslog، ثم اقرأ نهايته.

ls -l /var/log
sudo tail -n 50 /var/log/syslog

إذا لم تكن الوحدة أو السجل موجودين، فقد لا يكون cron مثبتاً أصلاً. غالباً لا تتضمن الصور السحابية المصغّرة والحاويات هذه الحزمة.

dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cron

السبب 1: لا يملك cron قيمة PATH الخاصة بك

يبني shell التفاعلي قيمة PATH من /etc/profile و~/.profile و~/.bashrc وكل ما تستدعيه هذه الملفات. لا يُنفَّذ أي من ذلك عند تشغيل مهمة cron. يبدأ cron الأمر ببيئة قصيرة خاصة به، لذلك لا يُعثر على البرنامج الموجود خارج أدلة النظام القياسية. ينطبق ذلك على أي شيء موجود ضمن /usr/local/bin أو /opt، وعلى مدير إصدارات لغة، أو بيئة Python افتراضية، أو مساحة عمل Go. تفشل المهمة في سطرها الأول، ويكتب shell خطأ بصيغة "not found"، وتختلف الصياغة الدقيقة حسب shell الذي شغّلها.

اعثر على المسار الفعلي لكل أمر تستخدمه مهمتك.

command -v docker
command -v node
readlink -f "$(command -v node)"

بعد ذلك، اكتب هذه المسارات المطلقة في المهمة، أو عيّن PATH مرة واحدة في أعلى crontab.

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool run

استخرج هذه القائمة من جهازك باستخدام echo "$PATH"، واحذف أي عنصر لا يوجد إلا داخل جلسة تفاعلية. توجد قاعدة مهمة هنا: لا يوسّع cron المتغيرات في أسطر التعيين هذه. يخزّن PATH=$PATH:/usr/local/bin النص الحرفي $PATH:/usr/local/bin، فتحتوي المهمة في النهاية على مسار بحث لا يضم أي دليل صالح للاستخدام. اكتب القائمة كاملة.

يحتاج مدير الإصدارات إلى أكثر من مجرد مسار. يثبّت nvm وpyenv وrbenv وasdf دالة shell أو دليلاً للـshims من .bashrc، ولا يقرأ cron هذا الملف مطلقاً. استدعِ الملف التنفيذي الخاص بالإصدار باستخدام مساره المطلق، أو استدعِ ملف تهيئة المدير في السطر الأول من برنامجك النصي.

السبب 2: تنهي علامة النسبة المئوية الأمر

في حقل الأمر داخل crontab، لا تُعدّ % محرفاً عادياً. تنهي أول % غير مسبوقة بمحرف هروب الأمر. يمرّر cron كل ما يلي إلى الأمر عبر الإدخال القياسي، وتتحول كل % لاحقة إلى سطر جديد. هذه ميزة فعلية في cron لإدخال نص قصير إلى برنامج، وهي أيضاً سبب كون اسم الملف الذي يتضمن التاريخ مثالاً كلاسيكياً على سطر crontab معطّل.

اكتب 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site، ولن يرى tar تاريخاً منسقاً. يقطع cron السطر عند أول %، لذلك تستقبل الصدفة استبدال أمر غير مكتمل، بينما يصل باقي السطر إليها عبر الإدخال القياسي. اهرب من كل علامة نسبة مئوية باستخدام شرطة مائلة عكسية.

0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/site

تقرأ طبقتان ذلك السطر الواحد بالترتيب. \% قاعدة cron، ويطبّقها cron قبل بدء أي شيء. أما $(date +\%F) فهو استبدال الأوامر، وتطبّقه الصدفة التي يبدأها cron لاحقاً. معرفة الطبقة المسؤولة عن كل محرف هي جوهر الحل.

الطريقة الأكثر أماناً هي إبقاء المنطق خارج crontab بالكامل. ضعه في برنامج نصي، حيث لا تحمل علامة النسبة المئوية معنى خاصاً.

#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/site

يحتوي سطر crontab عندئذٍ على مسار وإعادة توجيه فقط، ولا شيء آخر. إن أمكنك قراءة crontab بسرعة، فسيكون تصحيح أخطائه أسهل.

السبب 3: أي ملف crontab عدّلت؟

لا يوجد ملف crontab واحد. توجد عدة ملفات، بمالكين مختلفين وعدد حقول مختلف، وتكون المهمة المكتوبة في الملف الخطأ غير مرئية.

  • crontab -e يعدّل crontab للمستخدم الذي ينفّذ الأمر. أما sudo crontab -e فيعدّل crontab الخاص بـroot. وغالباً ما ينتهي شخصان يستكشفان المشكلة على الخادم نفسه إلى قراءة ملفين مختلفين.
  • يعرض sudo crontab -l -u deploy ملف crontab لمستخدم آخر. وهذا يتيح لك تأكيد ما هو مثبّت فعلياً للحساب الذي يفترض أن يشغّل المهمة.
  • يحتوي /etc/crontab وكل ملف داخل /etc/cron.d على حقل إضافي بين الجدول الزمني والأمر، وهو المستخدم الذي ستعمل المهمة بحسابه. إذا ألصقت سطراً من crontab مستخدم، يتكون من خمسة حقول، داخل /etc/cron.d، فسيُقرأ أول جزء من أمرك على أنه اسم مستخدم.
  • يجب أن تُسمّى الملفات داخل /etc/cron.d باستخدام الأحرف والأرقام والشرطات السفلية والواصلات فقط. يُتخطّى ملف اسمه backup.sh أو site.conf بسبب اسمه وحده. أعد تسميته إلى backup ثم افحص السجل مرة أخرى.
  • يجب أن تكون ملكية الملفات داخل /etc/cron.d لـroot، ويجب ألا تكون قابلة للكتابة من المجموعة أو المستخدمين الآخرين. يعرض ls -l /etc/cron.d هاتين المعلومتين معاً.
  • تتبع البرامج النصية الموضوعة داخل /etc/cron.daily والأدلة المماثلة له قاعدة التسمية نفسها، ويجب أن تحمل أيضاً بت التنفيذ. يؤدي غياب بت التنفيذ إلى تخطّي الملف بصمت.
  • يحدّد /etc/cron.allow و/etc/cron.deny من يُسمح له بتثبيت crontab أصلاً. إذا كان أي منهما موجوداً على خادمك، فاقرأه قبل افتراض أن مستخدمك مسموح له بذلك.

ثبّت crontab للمستخدم باستخدام الأمر crontab بدلاً من تعديل ملف spool يدوياً، لأن crontab يحلّل الملف قبل تثبيته. عند الحفظ، اقرأ النص الذي يعرضه الأمر لك. إذا رفض الملف، تبقى النسخة السابقة قيد التشغيل ولا يسري التغيير، وهذا يبدو تماماً كأن cron يتجاهلك.

يحدّد المالك أيضاً الصلاحيات. تنشئ المهمة الموجودة في crontab الخاص بـroot ملفات يملكها root، وقد لا يتمكن التطبيق الذي يقرأها من الكتابة إليها. ولا تستطيع مهمة موجودة في crontab لمستخدم عادي قراءة دليل لا يملكه إلا root. طابق المالك مع طبيعة العمل: تخص صيانة التطبيق حساب التطبيق نفسه، وهذا هو الأساس وراء استبدال wp-cron في WordPress بمهمة cron للنظام. يأتي نمط الملفات التي تنشئها المهمة من umask الذي ترثه، وهذه قيمة أخرى لا تطابق قيمة shell لديك، لذلك يجدر بك قراءة كيفية ضبط umask لصلاحيات الملفات إذا كان ناتج المهمة غير قابل للقراءة.

السبب 4: ذهب الإخراج إلى بريد لا يقرأه أحد

يجمع cron كل ما تكتبه المهمة إلى standard output وstandard error. إذا كتبت المهمة أي شيء، يسلّم cron هذا النص إلى نظام البريد المحلي، ويوجهه إلى مالك crontab أو إلى ما يحدده MAILTO. عادةً لا يكون هناك MTA (mail transfer agent) مثبت على VPS مبسّط، لذلك لا يوصله أي مكوّن. كان الخطأ موجوداً للحظة ثم اختفى دون أن يصل إلى أي مكان. وهذا هو السبب الكامل لظهور المهمة المعطلة وكأنها صامتة.

وجّه الإخراج إلى ملف تتحكم فيه بدلاً من ذلك.

0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

يضيف >> standard output إلى الملف. ويوجّه 2>&1 standard error إلى الوجهة الحالية لـstandard output، لذلك يجب أن يأتي بعد إعادة التوجيه. إذا عكست الترتيب وكتبت 2>&1 >> file، فسيحتفظ standard error بوجهته الأصلية، وسيكون الخطأ الذي تبحث عنه هو الجزء الذي لا يصل إلى الملف تحديداً.

يُعد journal وجهة جيدة أخرى. يكتب logger إلى syslog باستخدام وسم تختاره.

0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-site

اقرأه مجدداً باستخدام journalctl -t backup-site. يضع ذلك إخراج المهمة بجانب إدخالات cron، مما يسهّل تتبع التسلسل الزمني. إذا كنت تحتاج أيضاً إلى سجل يوضح أي شخص شغّل أي أمر على الخادم، فهذا نظام منفصل، ويغطيه تدقيق أوامر المستخدمين على خادمك.

يؤدي MAILTO="" في أعلى crontab إلى تعطيل البريد للمهام التي تليه. لا يفيد ضبط MAILTO على عنوان حقيقي إلا إذا كان هناك MTA يعمل، لذلك تحقّق من مغادرة البريد للخادم قبل الاعتماد عليه.

قاعدة واحدة أثناء تصحيح الأخطاء: لا تضف > /dev/null 2>&1 أبداً. فهذا هو السطر الأكثر شيوعاً في كل crontab، ويتخلص من الدليل الوحيد المتاح لديك. أعده لاحقاً إذا أردت، بعد أن تعمل المهمة.

السبب 5: يفترض البرنامج النصي وجود بيئة لا يوفّرها cron

بعد العثور على الأمر والتقاط مخرجاته، يبقى كل ما تمنحك إياه جلستك تلقائياً.

  • قد لا تكون الصدفة bash. تحقّق باستخدام ls -l /bin/sh. في Debian وUbuntu، يشير ذلك إلى dash، ولذلك يفشل اختبار الأقواس المزدوجة والمصفوفات وsource بسبب خطأ في الصياغة. أضف السطر #!/bin/bash إلى البرنامج النصي واستدعِ البرنامج النصي، أو عيّن SHELL في بداية crontab.
  • لا يكون دليل العمل هو الدليل الذي كنت موجوداً فيه. استخدم المسارات المطلقة في كل مكان، أو استخدم cd للانتقال إلى الدليل في السطر الأول من البرنامج النصي. المسار النسبي هو السبب الفردي الأكثر شيوعاً لكون المهمة «تعمل عند تشغيلها يدوياً».
  • تكون الإعدادات المحلية مختلفة عن إعدادات جلستك. أي أمر ينسّق تاريخاً أو رقماً أو يرتّب نصاً قد ينتج مخرجات مختلفة تحت LANG مختلف. إذا كانت خطوة لاحقة تحلّل تلك المخرجات، فعيّن الإعدادات المحلية في البرنامج النصي بدلاً من الاعتماد على الافتراضات.
  • لا توجد TTY (طرفية). قد تتوقف الأوامر التي تطلب تأكيداً أو تفتح محرراً أو تعرض شريط تقدم، أو قد تنتهي. أضف خيار التشغيل غير التفاعلي الذي توفّره الأداة.
  • لا يوجد SSH agent. لا يكون SSH_AUTH_SOCK موجوداً في بيئة cron، ولذلك يفشل أمر ssh أو rsync الذي كان يعمل لأن الوكيل لديك محمّل، في المصادقة الآن. امنح المهمة مفتاحاً خاصاً بها، ويجب أن يملكه مستخدم المهمة.
  • لا توجد حافلة جلسة للمستخدم، ولذلك يفشل systemctl --user من مهمة cron إلى أن يتم تعيين XDG_RUNTIME_DIR. تكون وحدة النظام هي الحل الأفضل.

في Fedora وRocky وAlma، يوجد سبب محتمل إضافي. يقيّد SELinux مهام cron، ولذلك تُرفض المهمة التي تصل إلى مسار ذي label غير متوقع حتى عندما تبدو أذونات الملف صحيحة. تحقّق من حالات الرفض باستخدام sudo ausearch -m avc -ts recent، واقرأ أساسيات SELinux للخادم قبل إيقاف أي شيء.

المسبار الذي يستغرق دقيقة واحدة ويعرض بيئة cron

توقف عن التخمين بشأن محتويات بيئة cron واقرأها مباشرة. اكتب برنامجاً نصياً يفرغ كل شيء، وجدوله ليعمل كل دقيقة، وانتظر، ثم اقرأ الملف.

cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.sh

أضف سطراً واحداً إلى crontab الخاص بالمستخدم الذي تعمل المهمة الفعلية بحسابه، مع استخدام مسارات مطلقة على جانبي السطر.

* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1

انتظر دقيقة، ثم اقرأ /home/deploy/cron-probe.log وقارنه بنتيجة تنفيذ الأوامر نفسها في shell الخاص بك. غالباً ما يفسر سطر PATH ودليل العمل والـlocale سبب الفشل وحدها. انتبه إلى تفصيلين في الإعداد: توجد علامات النسبة المئوية داخل البرنامج النصي، حيث لا تنطبق قاعدة cron، ومسار السجل قابل للكتابة من جانب مستخدم المهمة.

احذف سطر crontab فور حصولك على الإجابة. فالمهمة التي تعمل كل دقيقة وتضيف محتوى إلى ملف ستملأ قرصاً صغيراً، وستفعل ذلك بهدوء.

هل الجدول هو الذي قصدته؟

يبدأ سطر crontab الخاص بالمستخدم بخمسة حقول: الدقيقة، والساعة، ويوم الشهر، والشهر، ويوم الأسبوع. يتفاعل حقلان منها بطريقة تفاجئ بعض المستخدمين.

عندما يكون كل من يوم الشهر ويوم الأسبوع مقيّداً، أي لا يساوي أيٌّ منهما *، يشغّل cron المهمة عندما يطابق أيٌّ من الحقلين القيمة المحددة. لا يعني 0 0 13 * 5 «الجمعة 13». بل يشغّل المهمة عند منتصف الليل في اليوم 13 من كل شهر، وعند منتصف الليل كل يوم جمعة. للحصول على يوم محدد واحد، اترك أحد الحقلين على * واختبر الحقل الآخر داخل البرنامج النصي.

يستخدم cron المنطقة الزمنية للنظام. تأتي كثير من صور VPS مضبوطة على UTC (التوقيت العالمي المنسق)، لذلك تعمل المهمة التي جدولتها في الساعة 03:00 عند الساعة 03:00 UTC، وقد يكون ذلك في منتصف فترة بعد الظهر لديك. يطبع timedatectl المنطقة الزمنية التي يستخدمها خادمك فعلياً. تحقّق من إعدادك بدلاً من افتراض أنه يطابق إعداد حاسوبك المحمول.

هناك مشكلتان إضافيتان في الجداول تستحقان المعرفة. يعمل @reboot عند بدء cron نفسه، وهذا لا يحدث بالضرورة بعد جاهزية الشبكة، لذلك قد تفشل المهمة التي تحتاج إلى DNS أو مضيف بعيد عند الإقلاع، ثم تنجح في كل تشغيل يدوي لاحق. كذلك لا يمنع شيء مهمة بطيئة من البدء مجدداً بينما لا تزال النسخة السابقة منها تعمل. ضعها داخل قفل.

*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

يتوقف flock -n فوراً عندما يكون القفل محجوزاً، لذلك يتوقف التشغيل المتداخل بدلاً من التراكم فوق التشغيل الأول.

عندما يكون systemd timer هو الأداة الأنسب

يؤدي cron مهمة واحدة جيداً: تشغيل هذا الأمر في هذا الوقت. لكنه ضعيف في كل ما عدا ذلك. يوفّر timer السجل journal من دون أي إعادة توجيه، وحالة خروج يمكنك الاستعلام عنها لاحقاً، وترتيباً بالنسبة إلى network-online.target، وتأخيراً عشوائياً حتى لا تبدأ مئة خادم جميعاً في الثانية نفسها. عندما تحتاج مهمتك إلى أي من ذلك، يكون إعداد خدمة وtimer باستخدام systemd على VPS أقل جهداً من الدفاع عن سطر في crontab. وينطبق الأمر نفسه على سلوك إعادة المحاولة، لأن سياسات إعادة التشغيل في systemd تحدد ما يحدث بعد الفشل، بينما لا يقدّم cron أي إجابة عن هذا السؤال.

استخدم cron للمهام الصغيرة. انقل أي مهمة لها تبعيات أو سياسة لإعادة المحاولة إلى timer. يمكن لكليهما العمل على الخادم نفسه، لذلك لا تحتاج إلى إكمال هذه العملية في جلسة واحدة.

FAQ

لماذا تعمل مهمة cron يدوياً لكنها تفشل عند تشغيلها من cron؟

لأن بيئة shell وبيئة cron مختلفتان. يقرأ shell عند تسجيل الدخول /etc/profile و~/.bashrc، وهما يضبطان PATH واللغة المحلية ومتغيرات الوكيل. يبدأ cron الأمر من دون هذه الإعدادات، ومن دليل عمل مختلف، وأحياناً باستخدام shell مختلف. استخدم مسارات مطلقة لكل أمر، واضبط ما تحتاج إليه في بداية crontab أو داخل البرنامج النصي، وجدول مهمة اختبار تُنفَّذ بعد دقيقة واحدة وتشغّل env | sort وpwd وid إلى ملف سجل، حتى تتمكن من قراءة بيئة cron الفعلية بدلاً من تخمينها.

كيف أتحقق من أن cron شغّل مهمتي فعلاً؟

اقرأ سجل الخدمة. استخدم journalctl -u cron في Debian وUbuntu، أو journalctl -u crond في Fedora وRocky وAlma؛ وبعض الصور توجّه الرسائل عبر rsyslog إلى ملف ضمن /var/log بدلاً من ذلك. ابحث عن إدخال في الدقيقة التي يحددها الجدول، وتحقق من أنه يذكر أمرك. يعني غياب الإدخال أن cron لم يحمّل الجدول، لذا تأكد من أنك عدّلت crontab الصحيح. ويعني وجود إدخال من دون نتيجة أن الأمر بدأ ثم انتهى، لذا احفظ مخرجاته باستخدام إعادة التوجيه.

لماذا يتسبب date +%Y في فشل crontab؟

يتعامل cron مع % باعتباره محرفاً خاصاً في حقل الأمر. ينهي أول % غير محمي الأمر، ويمرر كل ما بعده إلى ذلك الأمر كإدخال قياسي، وتتحول كل % لاحقة إلى سطر جديد. لذلك لا يصل اسم الملف المنسق بالتاريخ إلى البرنامج الذي كتبته من أجله. احمِ كل علامة نسبة مئوية باستخدام \%، أو انقل الأمر إلى برنامج نصي واستدعِ البرنامج النصي من cron، لأن علامة النسبة المئوية لا تحمل معنى خاصاً داخل البرنامج النصي.

إلى أين تذهب مخرجات مهمة cron؟

تذهب إلى نظام البريد المحلي، وتُوجَّه إلى مالك crontab أو إلى الجهة التي يحددها MAILTO. لا تحتوي معظم صور VPS على وكيل نقل بريد مثبت، لذلك تُهمل الرسالة وتبدو المهمة صامتة. أعد توجيه المخرجات إلى ملف باستخدام >> /path/to/log 2>&1، مع الحفاظ على هذا الترتيب حتى يتبع الخطأ القياسي المخرجات القياسية، أو مررها عبر logger -t myjob ثم اقرأها باستخدام journalctl -t myjob. لا تستخدم > /dev/null 2>&1 أثناء استمرارك في تصحيح الأخطاء.

هل أستخدم cron أم مؤقت systemd؟

استخدم cron لأمر بسيط في وقت ثابت، خصوصاً إذا كنت قد تحتاج إلى نقله إلى جهاز لا يشغّل systemd. استخدم المؤقت عندما تريد حفظ المخرجات في journal من دون إعادة توجيه، أو تريد حالة خروج قابلة للاستعلام، أو ترتيب التشغيل بعد جاهزية الشبكة، أو تأخيراً عشوائياً لبدء التشغيل، أو سياسة لإعادة المحاولة بعد الفشل. يمكن لكليهما العمل على الخادم نفسه، لذا يمكنك نقل المهام واحدة تلو الأخرى عندما تبرر الحاجة ذلك.