Ders 19 / 21
Zamanlanmış Görevler
Yinelenen görev tanımı, sıklık sözcükleri ve zamanlayıcı tiki; istekte kaydedilen görevin tikte koşması, zamanı gelmemiş görevin tikte atlanması ve tik aralığının görev sıklığına koyduğu sınır.
Yayın Güncelleme
Bu derste
- Yinelenen bir görevi sıklık sözcüğüyle tanımlar ve zamanlayıcıya kaydeder
- İstekte kaydedilen görevin istek içinde değil zamanlayıcı tikinde koştuğunu ölçer
- Zamanı gelmemiş görevin tikte atlandığını ve tik aralığının görev sıklığını sınırladığını gösterir
İçindekiler
Dosya Depolama dersinde yüklenen dosya bir diske yazıldı ve bu iş yanıt gelmeden önce, isteğin içinde bitti. Her iş böyle değildir. Eski dosyaları temizlemek, her gece bir özet üretmek ya da her dakika bir sayacı sıfırlamak gibi işler hiçbir kullanıcı isteğine bağlı değildir; bir istek gelmese de belli aralıklarla çalışmaları gerekir. Bu ders şunu sorar: tekrarlanan bir iş, hangi isteğin içinde koşmuş sayılır?
Zamanlanmış görev (scheduled task), bir sıklık ile tanımlanan yinelenen bir iştir: her dakika, her saat, her gün. Görevi çalıştıran şey bir istek değil, düzenli aralıklarla çalışan bir zamanlayıcıdır (scheduler); her çalışmasına tik denir. Zamanlayıcı her tikte kayıtlı görevlere bakar ve zamanı gelmiş olanları koşturur. Bir istek yalnızca görevi kaydedebilir; görevin çalışma anı isteğin dışında, sonraki bir tiktedir. Kuyruklar ve İşler dersindeki iş de isteğin dışında koşmuştu, ama oradaki iş bir istekten doğmuştu ve bir kez çalışıyordu. Burada iş bir kez tanımlanır ve kendisini tekrar eder.
US8. Saat bir değişkendir: tik, geçerli an olarak bir tam sayı alır (saniye) ve sleep ya da gerçek saat çağrılmaz. Zamanlayıcının kendisi bir döngü, bir süreç ya da bir sistem aracı değildir; tik() yöntemi sürücü tarafından elle çağrılır. Bir görev, kaydedildiği anda ya da bir önceki çalışma anında başlayan bir aralığın sonunda çalışır; ilk çalışma kayıttan bir aralık sonradır, kayıt anında değildir.
US9. Üç sıklık sözcüğü tanımlıdır: her_dakika 60 saniye, her_saat 3600 saniye, her_gun 86400 saniye. Başka bir sözcük InvalidArgumentException fırlatır. Takvim saati, saat dilimi, ay ve gün adı, kümeleme ve aynı anda çalışan iki zamanlayıcının görevi iki kez koşturması modellenmez. Kaçırılan bir tiki telafi eden bir mekanizma da yoktur; bu varsayımın sonucu Sınırlayıcı Ölçüm bölümünde ölçülür.
Ölçüm Çekirdeği
Kurs boyunca her derste aynı ölçüm kullanılır. kostu.php beklenen bir parçanın bu istekte koşup koşmadığını kaydeder; dosya her derste birebir verilir ve çıktı bloğu yoktur, yalnız diske yazılır. Gunluk üç liste tutar: isteğin yoluna konanlar, istek içinde çağrılanlar ve isteğin dışına bırakılanlar. sinifla() bunlara sırayla bakar. Karar sırası sinifla()’nın kendisidir: koştu, ertelendi, atlandı, bağlanmadı. Parçayı geliştirici adlandırır; ölçülen, geliştiricinin bu istekte koşmasını beklediği parçalardır. Bu derste “istek” iki yerde olur: görevi kaydeden gerçek istek ve zamanlayıcının her tiki. ertele() kayıt anında, yola() ve kostu() ise tikte çağrılır.
<?php // kostu.php — beklenen bir parça bu istekte koştu mu? declare(strict_types=1); final class Gunluk { public static array $yol = []; // isteğin yoluna konan parçalar public static array $kostu = []; // istek içinde çağrılanlar public static array $ertelenen = []; // isteğin dışına bırakılanlar public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; } public static function yola(string $parca): void { self::$yol[] = $parca; } public static function kostu(string $parca): void { self::$kostu[] = $parca; } public static function ertele(string $parca): void { self::$ertelenen[] = $parca; } } function sinifla(string $parca): string { return match (true) { in_array($parca, Gunluk::$kostu, true) => 'koştu', in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi', in_array($parca, Gunluk::$yol, true) => 'atlandı', default => 'bağlanmadı', }; }
Görev Tanımı ve Zamanlayıcı
zamanlayici.php üç sınıf tanımlar. Sayac bir işin kaç kez çalıştığını sayar; ölçülen şey işin gövdesi değil, işin çalışıp çalışmadığıdır. Zamanlayici::kaydet() görevi bir ad, bir sıklık sözcüğü ve bir fonksiyonla alır, sıklığı saniyeye çevirir, ilk çalışma anını simdi + aralik olarak hesaplar ve görevi isteğin dışına bırakılanlar listesine yazar. Zamanlayici::tik() yeni bir ölçüm turu açar, her görevi yola koyar ve geçerli an görevin sonraki değerine ulaşmamışsa göreve dokunmadan sonrakine geçer. Ulaşmışsa sonraki çalışma anını simdi + aralik olarak günceller, görevi koştu olarak işaretler ve fonksiyonu çağırır. RaporDenetleyicisi ise bir istekte rapor görevini her dakika çalışacak biçimde kaydeder ve 202 döndürür.
Yanıtın 202 olması anlamlıdır: istek bir şeyi kabul etti, ama yapmadı. Denetleyici görevi koşturmaz ve koşturmasını beklemez; yanıt, görev henüz çalışmadan dönmüştür. Bu, Kuyruklar ve İşler dersindeki yanıtla aynı yapıdadır, tek fark çalışma anını bir kuyruk işçisinin değil bir saat değerinin belirlemesidir.
<?php // zamanlayici.php — görev tanımı, sıklık sözcükleri ve zamanlayıcı tiki declare(strict_types=1); require_once 'kostu.php'; final class Sayac { public array $say = []; public function artir(string $ad): void { $this->say[$ad] = ($this->say[$ad] ?? 0) + 1; } public function oku(string $ad): int { return $this->say[$ad] ?? 0; } } final class Zamanlayici { private const ARALIK = ['her_dakika' => 60, 'her_saat' => 3600, 'her_gun' => 86400]; private array $gorevler = []; public function kaydet(string $ad, string $siklik, callable $is, int $simdi): void { if (!isset(self::ARALIK[$siklik])) { throw new InvalidArgumentException("bilinmeyen siklik: $siklik"); } $aralik = self::ARALIK[$siklik]; $this->gorevler[$ad] = ['aralik' => $aralik, 'sonraki' => $simdi + $aralik, 'is' => $is]; Gunluk::ertele($ad); } public function tik(int $simdi): void { Gunluk::yeniIstek(); foreach (array_keys($this->gorevler) as $ad) { Gunluk::yola($ad); if ($simdi < $this->gorevler[$ad]['sonraki']) { continue; } $this->gorevler[$ad]['sonraki'] = $simdi + $this->gorevler[$ad]['aralik']; Gunluk::kostu($ad); ($this->gorevler[$ad]['is'])(); } } } final class RaporDenetleyicisi { public function __construct(private Zamanlayici $zamanlayici, private Sayac $sayac) {} public function kaydet(int $simdi): array { Gunluk::yeniIstek(); $this->zamanlayici->kaydet('rapor', 'her_dakika', fn() => $this->sayac->artir('rapor'), $simdi); return [202, 'kaydedildi']; } }
İstekte Kaydedilen Görev Tikte Koştu
Sürücü isteği saat 0’da yapar, sonra zamanlayıcıyı saat 60’ta tikler. İstekten hemen sonra sınıf ve sayaç okunur, tikten sonra yine okunur. İki okuma arasındaki tek fark, zamanlayıcının çalışmış olmasıdır.
<?php // erteleme.php — istekte kaydedilen görev tikte koşar require_once 'zamanlayici.php'; $sayac = new Sayac(); $zamanlayici = new Zamanlayici(); $denetleyici = new RaporDenetleyicisi($zamanlayici, $sayac); $yanit = $denetleyici->kaydet(0); printf("istek (saat 0) -> %d %s\n", $yanit[0], $yanit[1]); printf(" rapor %s, işin çalışma sayısı: %d\n", sinifla('rapor'), $sayac->oku('rapor')); $zamanlayici->tik(60); printf("tik (saat 60)\n"); printf(" rapor %s, işin çalışma sayısı: %d\n", sinifla('rapor'), $sayac->oku('rapor'));
istek (saat 0) -> 202 kaydedildi rapor ertelendi, işin çalışma sayısı: 0 tik (saat 60) rapor koştu, işin çalışma sayısı: 1
İstekten hemen sonra rapor ertelendi: görev kaydedildi ama bu istekte çağrılmadı; sayaç 0’dır. Tikten sonra aynı parça koştu: görev yola girdi, zamanı gelmişti ve fonksiyon çağrıldı; sayaç 1’dir. Dersin ölçü sınıfı bu yüzden ertelendi olarak okunur: istek açısından iş yapılmadı, yalnızca yapılacağı kayda geçti. “Ertelendi” ile “koştu” arasında kalan süre, görevin sıklığından küçük olamaz; ilk çalışma bir aralık sonradır. Ölçüm, işin sonunda çalışıp çalışmadığını söylemez; yalnızca hangi turda çalıştığını söyler.
Karşıt: Zamanı Gelmemiş Görev Tikte Atlandı
Zamanlayıcının her tiki görevi çalıştırmaz. Sürücü aynı kayıttan sonra zamanlayıcıyı saat 30, 59, 60 ve 61’de tikler. Her tikten sonra rapor sınıfı ve toplam çalışma sayısı yazılır.
<?php // atlama.php — zamanı gelmemiş görev tikte atlanır require_once 'zamanlayici.php'; $sayac = new Sayac(); $zamanlayici = new Zamanlayici(); (new RaporDenetleyicisi($zamanlayici, $sayac))->kaydet(0); foreach ([30, 59, 60, 61] as $saat) { $zamanlayici->tik($saat); printf("tik (saat %2d) rapor %-10s toplam: %d\n", $saat, sinifla('rapor'), $sayac->oku('rapor')); }
tik (saat 30) rapor atlandı toplam: 0 tik (saat 59) rapor atlandı toplam: 0 tik (saat 60) rapor koştu toplam: 1 tik (saat 61) rapor atlandı toplam: 1
Saat 30’da ve 59’da görev yola girdi ama sonraki değerine ulaşmadığı için çağrılmadı: atlandı. Saat 60’ta zamanı geldi ve koştu. Saat 61’de görev yine yola girdi, ama sonraki değeri artık 120’dir ve bu tikte de atlandı. Toplam 1’de kaldı. İlk üç satır, dersin gözleminin karşıtıdır: aynı kod, aynı kayıt, ama tikin saati farklı olduğu için farklı sınıf.
Buradaki “atlandı” ile Dosya Depolama dersindeki “bağlanmadı” arasındaki fark, yolun varlığıdır. Burada görev her tikte yola girer; çünkü zamanlayıcı onu her seferinde değerlendirir. Atlanması, değerlendirilmediği anlamına gelmez; değerlendirildiği ve zamanı gelmediği anlamına gelir. Bu ayrım önemlidir: bir tikte görev yola girmemişse, kaydı yoktur ya da zamanlayıcı hiç çalışmamıştır.
Sınırlayıcı Ölçüm
Zamanlayıcı bir tiki kaçırırsa ne olur? siklik.php üç görevi, üç sıklık sözcüğüyle, iki saatlik bir tik akışında çalıştırır; akışı iki farklı tik aralığıyla iki kez kurar. Birinci akışta zamanlayıcı her 60 saniyede, ikincisinde her 300 saniyede tiklenir. Görevler kayıt anı 0’dan başlar.
<?php // siklik.php — üç sıklık sözcüğü, iki saatlik tik akışı, iki ayrı tik aralığı require_once 'zamanlayici.php'; foreach ([60, 300] as $tikAraligi) { $sayac = new Sayac(); $zamanlayici = new Zamanlayici(); foreach (['her_dakika' => 'dakikalik', 'her_saat' => 'saatlik', 'her_gun' => 'gunluk'] as $siklik => $ad) { $zamanlayici->kaydet($ad, $siklik, fn() => $sayac->artir($ad), 0); } for ($saat = $tikAraligi; $saat <= 7200; $saat += $tikAraligi) { $zamanlayici->tik($saat); } printf("tik aralığı %3d sn: dakikalik %3d, saatlik %d, gunluk %d\n", $tikAraligi, $sayac->oku('dakikalik'), $sayac->oku('saatlik'), $sayac->oku('gunluk')); }
tik aralığı 60 sn: dakikalik 120, saatlik 2, gunluk 0 tik aralığı 300 sn: dakikalik 24, saatlik 2, gunluk 0
Her 60 saniyede tiklenen zamanlayıcıda dakikalik görev 120 kez, saatlik 2 kez, gunluk hiç çalışmadı: iki saatte 120 dakika, 2 saat ve günün tamamlanmasına çok var. Her 300 saniyede tiklenen zamanlayıcıda dakikalik görev yalnız 24 kez çalıştı; bunun nedeni görevin her dakika değil, her tikte en çok bir kez çalışabilmesidir. İki saatte 24 tik vardır ve her tikte görev en fazla bir kez koşar. Kaçırılan 96 koşu telafi edilmedi: bu derste kaçırılan bir tiki geri alan bir mekanizma yoktur. saatlik görev iki akışta da 2 kez çalıştı, çünkü 300 saniye 3600’ü böler ve tikler saat başlarına denk gelir.
Bu ölçümün sonucu şudur: sıklık sözcüğü görevin en çok ne kadar sık koşabileceğini belirler; fiilen koşma sıklığını ise zamanlayıcının tik aralığı sınırlar. Ölçüm yalnız bu modeldeki sonucu verir. Başka bir zamanlayıcı kaçırılan koşuları sırayla telafi edebilir ya da bir tikte aynı görevi birden çok kez koşturabilir; o davranış burada ölçülmemiştir ve sürücü yalnızca bu dersin modeline aittir.
Tik aralığının öteki ucu da ölçülebilir: iki tik aynı anda gelirse ne olur? cift.php aynı kayıttan sonra zamanlayıcıyı iki kez saat 60’ta tikler. Birinci tikte görev koşar ve sonraki değeri 120’ye taşınır; ikinci tikte geçerli an hâlâ 60’tır ve 60, 120’den küçüktür.
<?php // cift.php — aynı anda iki tik: ikinci tik görevi yeniden koşturmaz require_once 'zamanlayici.php'; $sayac = new Sayac(); $zamanlayici = new Zamanlayici(); (new RaporDenetleyicisi($zamanlayici, $sayac))->kaydet(0); foreach ([1, 2] as $tur) { $zamanlayici->tik(60); printf("%d. tik (saat 60) rapor %-10s toplam: %d\n", $tur, sinifla('rapor'), $sayac->oku('rapor')); }
1. tik (saat 60) rapor koştu toplam: 1 2. tik (saat 60) rapor atlandı toplam: 1
İkinci tikte görev yola girdi ama çağrılmadı: atlandı, toplam 1’de kaldı. Aynı anda gelen çift tik görevi iki kez koşturmaz; çünkü çalışma anı görevin kendi sonraki değerinde tutulur, tik sayısında değil. Bu koruma yalnız tek bir zamanlayıcı nesnesi için geçerlidir: sonraki değeri o nesnenin içinde durur ve iki ayrı zamanlayıcı süreci aynı görevi aynı anda koşturabilir. US9 bu durumu kapsam dışı bıraktı ve bu bölümdeki ölçüm de onu kapsamaz.
Özet
- Bu dersin ölçü sınıfı ertelendi: istekte kaydedilen
raporgörevi istek içinde çağrılmadı, sayaç 0 kaldı ve görev saat 60 tikinde koştu. - Zamanlanmış görev bir sıklık sözcüğüyle (
her_dakika,her_saat,her_gun) tanımlanır ve ilk çalışma kayıttan bir aralık sonradır. - Karşıt: zamanı gelmemiş görev saat 30, 59 ve 61 tiklerinde yola girdi ama çağrılmadı ve atlandı; saat 60’ta koştu.
- Sınırlayıcı ölçümde 60 saniyelik tik akışı
dakikalikgörevi 120 kez, 300 saniyelik akış 24 kez koşturdu; kaçırılan koşular telafi edilmedi. - Aynı anda gelen çift tik görevi iki kez koşturmadı; ikinci tikte görev atlandı.
- Görevin sıklığı bir hedeftir, bir garanti değildir; fiilen koşma sıklığını zamanlayıcının tik aralığı sınırlar.
Sonraki Adım
Zamanlanmış görevler, kullanıcının beklemediği işleri zamana bağlar. Ama zaman, bir uygulamanın yanıtını biçimlendiren tek değişken değildir. Bu dersin görevi bir kullanıcıya bildirim gönderiyor olsaydı, bildirimin hangi dilde yazılacağı da bir seçim olurdu. Peki istenen dilde bir metin yoksa ne olur: hata mı, boş bir yanıt mı, başka bir dilden bir metin mi? Yerelleştirme dersi dil dosyalarından, yer tutuculardan ve çoğul biçimlerden metin kurar ve istenen dilde bulunmayan anahtarın yanıta nasıl bağlandığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.