Bir PDFium Bileşeni görüntüleyicisinde bir yazdırma işi sırasında İptal'e tıklamak bazen hiçbir şey yapmıyordu: döngü kalan her sayfayı ve kopyayı render etmeye devam ediyordu ve iş yine de yazıcıya ulaşıyordu. Neden, Free Pascal'ın for döngüsüydü; bu, üst sınırını yalnızca döngü girişinde bir kez sabitler; bu yüzden CopyCount veya ToPage'in arkasındaki değişkeni döngü ortasında sıfırlamak zaten çalışan hiçbir şeyi değiştirmedi. For döngüsü sınırları hatası, dört başka çapraz derleyici tuzağını üreten aynı PDFiumPas Delphi/FPC uyumluluk denetiminden çıkan beşinci tuzaktır ve o dördünden farklı olarak tamamen bir görüntüleyicinin yazdırma rutininin içinde yaşar -yazıcının hiç durmadığını gerçekten fark etmek, uzun bir işi İptal'i basılı tutan bir testçi gerektirdi
İptal'e tıklandıktan sonra yazdırma işi neden devam ediyor?
Yazdırma işi devam etti çünkü iptal kontrolü yalnızca döngü sınırlarını besleyen değişkenleri sıfırladı, Free Pascal'ın her döngü başladığında zaten kilitlediği döngülerin kendilerini değil. PDFViewer ve MultiPageViewer demolarındaki Yazdır düğmesinin arkasındaki SpeedButtonPrintClick işleyicisi, her işi üç iç içe döngüden kurar: harmanlanmış kopya setleri üzerinde bir dış döngü, sayfalar üzerinde bir orta döngü ve aynı sayfanın harmanlanmamış kopyaları üzerinde bir iç döngü. PrintDialog.Collate, hangi sayacın -CollateCopyCount veya CopyCount- gerçekte istenen kopya sayısını tuttuğuna karar verir, diğeri birde kalırken. İptal etmek üç düzeyin hepsine aynı anda ulaşmak zorundadır ve bu kodun ilk sürümü bunu, Cancel true olduğu anda sınır değişkenlerinin kendilerini sıfırlayarak yapmayı denedi
for CollateCopy:= 1 to CollateCopyCount do
for Page:= FromPage to ToPage do
for Copy:= 1 to CopyCount do
begin
// ... render the page and send it to the printer ...
Application.ProcessMessages;
if Cancel then
begin
CollateCopyCount:= 0;
ToPage:= 0;
CopyCount:= 0;
end;
end;
Printer.EndDoc; // runs whether or not Cancel fired
Niyet yeterince açık okunur: kaç harmanlamanın, sayfanın ve kopyanın kaldığını tanımlayan sayaçların hepsi sıfıra düşerse, döngülerin işi tükenmesi ve kendiliğinden düşmesi gerekir. Printer.EndDoc daha sonra döngüden sonra nasıl bittiğine bakılmaksızın koşulsuz çalıştı; bu yüzden bir kullanıcının durdurduğuna inandığı bir iş bile, tıklama fark edilmeden önce her sayfa render edilmiş olarak spool'a yine de gönderildi
Free Pascal bir for döngüsü başladığında neyi kilitler
Free Pascal, bir for döngüsünün son değerini tam olarak bir kez, döngü başladığı anda değerlendirir ve o döngünün ömrü boyunca bir daha asla. for Page := FromPage to ToPage do, kaç yineleme çalıştıracağını hesaplamak için ToPage'i bir kez okur ve bundan sonra döngünün ToPage adlı bir değişkenle daha fazla ilgisi kalmaz, yalnızca zaten yakaladığı yineleme sayısıyla ilgilenir. Döngü gövdesinin içinden ToPage := 0 ayarlamak, çalışan döngünün artık danışmadığı bir değişkeni değiştirir ki bu, Cancel true iken yazıcının birkaç yineleme daha, bazen hepsi, sayfa almaya devam edebilmesinin tam nedenidir
Bu örüntü, C veya C++'tan taşınacak tamamen makul bir alışkanlıktır ve PDFium Bileşeni'nin C++Builder görüntüleyici demosu, karşılaştırmayı doğrudan yapacak kadar Pascal olanı yakından yansıtır. Onun for (Copy = 1; Copy <= CopyCount; Copy++)'ı, her geçişte Copy <= CopyCount'u CopyCount'un canlı değerine karşı yeniden test eder; bu yüzden orada sayacı sıfırlamak bir sonraki kontrolde döngüyü gerçekten bitirir. C++Builder demosu özdeş sıfırla-sayaçları kodunu taşıyordu ve işe yarıyordu ki bu, aynı fikri aynı depoda hemen yanında oturan Pascal derlemesinde yeniden kullanmayı tam olarak güvenli gösteren şeydi
Delphi derlemesi neden aynı hataya çarpmadı?
Delphi PDFViewer demosu hiçbir zaman bir döngünün değişen bir sınırı fark etmesine bağımlı olmadı, çünkü iptal yolu bir karşılaştırma yerine bir istisna aracılığıyla geri sarılır. En içteki döngüsü, Cancel true olduğu anda RTL'nin Abort yordamını çağırır; bu, tüm üç iç içe for döngüsünden doğrudan tüm yazdırma bloğunu saran bir işleyiciye yayılan sessiz bir EAbort fırlatır
Printer.BeginDoc;
try
for CollateCopy:= 1 to CollateCopyCount do
for Page:= FromPage to ToPage do
for Copy:= 1 to CopyCount do
begin
// ... render the page and send it to the printer ...
Application.ProcessMessages;
if Cancel then
Abort; // raises EAbort, unwinds all three loops at once
end;
Printer.EndDoc;
except
on E: EAbort do
Printer.Abort;
else
begin
Printer.Abort;
raise;
end;
end;
Bir istisna, fırlatıldığı noktadan onu yakalayan işleyiciye kaç for döngüsünün ayırdığını umursamaz ki bu tam olarak bu sorunun ihtiyaç duyduğu özelliktir. Bu sağlamlık, yukarıda açıklanan sınır-kilitleme davranışına karşı kasıtlı bir savunma değildi -Delphi demosunun yazarı basitçe farklı bir araca başvurdu. İstisna tabanlı çıkış hâlâ daha sağlam örüntü olarak adlandırılmaya değer: sonradan eklenen bir dördüncü iç içe geçme düzeyine sağ çıkar, oysa manuel olarak yerleştirilmiş bir Break ifadeleri zinciri, döngü iç içe geçmesi her değiştiğinde hatırlanmak ve yeniden eklenmek zorundadır
Düzeltme: her iç içe geçme düzeyinde Break, bir PrintSucceeded bayrağıyla kapılanmış
PDFiumPas v2.27.0'da gönderilen düzeltme, Lazarus ve C++Builder görüntüleyici demolarında üç döngülü yapıyı korur ama iptal etmeyi her düzeyde açık hale getirir ve döngünün durmasını, işin gönderilmesinden, döngü tamamen bittikten sonra ancak okunan bir bayrağa ayırır
Printer.BeginDoc;
try
Cancel:= False;
for CollateCopy:= 1 to CollateCopyCount do
begin
for Page:= FromPage to ToPage do
begin
for Copy:= 1 to CopyCount do
begin
// ... render the page and send it to the printer ...
Application.ProcessMessages;
if Cancel then
Break;
end;
if Cancel then
Break;
end;
if Cancel then
Break;
end;
PrintSucceeded:= not Cancel;
finally
if PrintSucceeded then
Printer.EndDoc
else
Printer.Abort;
end;
PrintSucceeded, üç kat iç içe geçmiş döngü çıktıktan hemen sonra, yalnızca not Cancel'dan kasıtlı olarak bir kez hesaplanır. Döngü gövdesinin içindeki hiçbir şey işin başarılı olup olmadığına kendi başına karar veremez -döngü yalnızca iki şekilde bitebilir, harmanlamalar, sayfalar ve kopyalar tükenerek veya Cancel'ın tetiklediği Break zincirine çarparak- ve PrintSucceeded, döngü ilerledikçe izlemek yerine sonucu olaydan sonra okur. Onu bu şekilde hesaplamak, finally bloğunun Printer.EndDoc ile Printer.Abort arasındaki seçimini güvenilir kılan şeydir: döngü gerçekten yerleşmeden önce hiçbir zaman tetiklenmez
Kendi iptal-güdümlü yazdırma döngülerinizi denetlemek
Üç kontrol bu tek yazdırma rutininin çok ötesine seyahat eder. Bir Pascal for döngüsünün başladıktan sonra değişen bir sınır değişkenini fark edeceğini asla varsaymayın; bir döngünün erken bitmesi gerekiyorsa, bunu doğrudan, yalnızca en içteki değil, iptalin geçmesi gereken her iç içe geçme düzeyinde Break ile söyleyin. İç içe geçme, kaçırılmış bir Break'in makul olduğu kadar derin olduğunda, bir istisna gibi yapı gereği geri saran bir kaçış mekanizmasını tercih edin -Delphi demosunun Abort/EAbort çifti bunu bedavaya aldı. EndDoc gibi herhangi bir işleme adımını, döngünün içinde değil, kesinlikle ondan sonra hesaplanan bir bayrağın arkasına kapılayın; böylece erken duran bir iş, hiçbir zaman bitmiş bir işle karıştırılamaz
Bu döngüyü düzelten aynı inceleme, dokuz görüntüleyici demosunun bir iptal düğmesi görünürken klavyeyi nasıl ele aldığını da sıkılaştırdı: artık Esc dışındaki her tuşu yutuyorlar; bu yüzden aktif bir yazdırma veya arama sırasında rastgele bir Ctrl+P veya Ctrl+F artık üzerine ikinci birini başlatamıyor. Bu yeniden giriş düzeltmesi farklı bir mekanizmaya sahip farklı bir hatadır, ama Cancel'ın işlem ortasında gerçekte ne yaptığının aynı incelemesinden çıktı. Her iki düzeltmeden de taşınmaya değer alışkanlık, bir kodlama alışkanlığından çok bir test alışkanlığıdır: paylaşılan bir yazdırma rutininin gerçekte gönderildiği her derleyicide iptal etmeyi çalıştırın, çünkü sayaçları temizleyip döngünün fark etmesine güvenmek gibi bir fikir, C++Builder altında testten sağ çıkabilir, doğrulanmadan bir Free Pascal derlemesine ulaşabilir, kaynakta özdeş okunabilir ve biri İptal'i işin yine de bittiğini görecek kadar uzun basılı tutana kadar kimsenin görmediği bir şekilde başarısız olabilir. Bu iptal mantığının üzerine oturduğu yazdırma kurulumu için, PDFium VCL bileşeniyle PDF belgelerini yazdırma üzerine rehber gerisini ele alır
Bu yazdırma-iptali düzeltmesi, her sürümde aynı demo paketini üç derleyicinin hepsinde çalıştıran ve bunun gibi boşlukları bir destek bileti yerine bir derleme matrisiyle yakalayan, Delphi, C++Builder ve FPC/Lazarus için PDFium Bileşeni'nin bir parçası olarak gönderildi