HotPDF'in THPDFBackgroundRenderer sınıfı, yüklenmiş PDF sayfalarını bir işçi iş parçacığında bit eşlemlere oluşturan bir TThread türevidir; böylece bir Delphi görüntüleyici, bir sayfa arka planda hâlâ rasterleştirilirken kaydırmaya ve yeniden boyamaya devam edebilir. THPDFBackgroundRenderer.RequestPage bu işçi iş parçacığı için bir sayfa dizinini kuyruğa alır, CancelAll hâlâ bekleyen her şeyi düşürür ve GetCachedBitmap, çağıranın sahip olduğu ve serbest bırakması gereken bitmiş bir bit eşlemi geri verir. İki yüz sayfalık taranmış bir sözleşmeyi yalnızca UI iş parçacığında yazdırma çözünürlüğünde kaydırın ve her sayfa çevirme, GDI çizimi bitirene kadar pencereyi duraklatır — bu tam olarak THPDFBackgroundRenderer'ın ortadan kaldırmak için var olduğu takılmadır
PDF sayfalarını neden arka plan iş parçacığında oluşturmalı?
Bir arka plan iş parçacığı karmaşıklığını hak eder çünkü HotPDF'in sayfa oluşturucusu, kimse fark etmeden dönen ucuz bir bit eşlem kopyası değil, gerçek bir içerik akışı yorumlayıcısıdır: PDF operatörlerini gezer, bir grafik durumu yığını tutar ve yolları, görüntüleri ve glifleri yüklenmiş PDF sayfalarını bir TBitmap'e oluşturma'da ele alınan aynı motor olan GDI üzerinden rasterleştirir. Bu işi bir kaydırma veya boyama işleyicisi içinde eşzamanlı çalıştırın ve çağrı dönene kadar mesaj döngüsü pompalamayı durdurur — donmuş bir pencerenin gerçekte ne olduğu budur. Oluşturma çağrısı içinde Application.ProcessMessages'i bırakmak bunu düzeltmez: mesaj kuyruğunun boşalmasına izin verir, ancak oluşturmanın kendisi yine de çağıran iş parçacığına aittir; bu yüzden pencere eski içeriği daha hızlı yeniden boyar, gerçek iş ise hiçbir yere ilerlememiştir. Gerçekten yavaş bir oluşturma sırasında bir görüntüleyiciyi duyarlı tutmanın tek yolu, o oluşturmayı başka bir yerde çalıştırmaktır; bu yüzden THPDFBackgroundRenderer bir callback veya bir zamanlayıcı yerine bir TThread alt sınıfı olarak var
Kaydırılan bir görüntüleyici için bir istek kuyruğu kurmak
THPDFBackgroundRenderer.Create, yüklenmiş THotPDF örneğini ve o oluşturucunun tüm ömrü boyunca sabit kalan bir DPI'yi alır; böylece bir örnek üzerinden kuyruğa alınan her sayfa tek bir çözünürlükte oluşturulur; yakınlaştırmayı destekleyen bir görüntüleyicinin, yakınlaştırma seviyesi her değiştiğinde yeni bir DPI özelliğine değil, yeni bir oluşturucuya ihtiyacı vardır. RequestPage bir sayfa dizinini dahili bir kuyruğa ekler ve hemen döner: kendisi hiçbir oluşturma yapmaz ve UI iş parçacığına asla dokunmaz. Execute — Start'ı çağırdığınızda HotPDF'in çalıştırdığı devralınmış TThread giriş noktası — bu kuyruğun önünden bir seferde bir dizin çeker, belgenin sayfa önbelleği üzerinden onu oluşturur ve GetCachedBitmap'in daha sonra geri verebilmesi için sayfaya göre dizinlenmiş bir kopya saklar
type
TViewerForm = class(TForm)
RenderPollTimer: TTimer;
procedure RenderPollTimerTimer(Sender: TObject);
private
FDoc: THotPDF;
FRenderer: THPDFBackgroundRenderer;
FPendingPage: Integer;
procedure RequestPageWindow(CenterPage: Integer);
end;
procedure TViewerForm.RequestPageWindow(CenterPage: Integer);
var
I: Integer;
begin
if FRenderer <> nil then
begin
FRenderer.CancelAll;
FRenderer.Free;
end;
FRenderer := THPDFBackgroundRenderer.Create(FDoc, 150);
for I := CenterPage - 1 to CenterPage + 1 do
if (I >= 0) and (I < FDoc.LoadedPageCount) then
FRenderer.RequestPage(I);
FPendingPage := CenterPage;
FRenderer.Start;
end;
procedure TViewerForm.RenderPollTimerTimer(Sender: TObject);
var
Bmp: TBitmap;
begin
if FRenderer = nil then Exit;
Bmp := FRenderer.GetCachedBitmap(FPendingPage);
if Bmp <> nil then
begin
PageImage.Picture.Bitmap.Assign(Bmp);
Bmp.Free;
end;
end;
GetCachedBitmap, o sayfanın kopyası hazır olana kadar nil döndürür; bu yüzden yukarıdaki gibi bir zamanlayıcı-üzerinde-yoklama deseni yeterlidir; bağlanacak ayrı bir hazır olayı yoktur, HotPDF bunu daha büyük bir bildirim API'si yerine sade bir nil kontrolüyle çözer. Bir sonraki bölüm, CancelAll ve o Free çağrısının gerçekte ne yaptığını ele alır, çünkü sayfalar sırasız oluşturulmaya başladığında veya kuyruğun boşalabileceğinden daha hızlı bir kaydırma gerçekleştiğinde her ikisi de önem kazanır
Tek bir sayfa için tek çağrılık kısayol
THotPDF.RenderLoadedPageToBitmapAsync, THPDFBackgroundRenderer'a doğrudan dokunmadan tam olarak bir sayfa ateşlemenin yaygın durumu için var olur: oluşturucuyu dahili olarak inşa eder, RequestPage'i bir kez çağırır, iş parçacığını başlatır ve TThread referansını çağırana döndürür; çağıran ona sahiptir ve serbest bırakmaktan sorumludur. Sonucu almak, oluşturucunun kendi GetCachedBitmap'i yerine THotPDF.GetLoadedCachedRenderedBitmap üzerinden geçer, çünkü GetLoadedCachedRenderedBitmap, belgenin sayfa dizini ve DPI ile anahtarlanan paylaşılan önbelleğini okur; bu, RenderLoadedPageToBitmapCached'in ve yerleşik önceden getiricinin zaten doldurduğu aynı önbellektir — görüntüleyicinin başka bir kısmının o DPI'de zaten oluşturduğu bir sayfa, henüz işletim sistemi tarafından zamanlanmamış olsa bile, az önce başlatılan arka plan iş parçacığından önce hemen geri gelebilir
// A simpler alternative to the queue above, for one page at a time.
procedure TViewerForm.RequestSinglePage(PageIndex: Integer);
begin
if FAsyncWorker <> nil then
FAsyncWorker.Free; // waits if a prior page is still rendering
FAsyncWorker := Pdf.RenderLoadedPageToBitmapAsync(PageIndex, 150);
FPendingPage := PageIndex;
end;
procedure TViewerForm.AsyncPollTimerTimer(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := Pdf.GetLoadedCachedRenderedBitmap(FPendingPage, 150);
if Bmp <> nil then
begin
PageImage.Picture.Bitmap.Assign(Bmp);
Bmp.Free;
end;
end;
Zaten kuyruğa alınmış bir sayfayı iptal edebilir misiniz?
CancelAll yalnızca kuyrukta hâlâ bekleyen işleri kaldırır; HotPDF'in zaten önden çekip oluşturma çağrısına verdiği bir sayfa tamamlanmaya devam eder, çünkü THPDFBackgroundRenderer'ın zaten devam eden işi kesintiye uğratacak bir mekanizması yoktur. Bu pratikte makul bir uzlaşmadır — tek bir sayfa oluşturma, önceliklendirmeyi eklenen karmaşıklığa değer kılacak kadar uzun olmaz — ancak her kaydırma olayında CancelAll'ı ateşleyen hızlı bir kaydırma, her iptal anında hangi tek sayfa oluşturma sürecindeyse onun bedelini yine de öder. Resmi referans bu konuda açıktır: zaten çalışmakta olan oluşturma, iş parçacığı sonlanmadan önce bitebilir
Execute'ın gözden kaçması kolay ikinci bir davranışı vardır: döngü, kuyruğu boş bulur bulmaz çıkar, boşta kalıp daha fazla iş gelmesini beklemez. Bu yüzden bir THPDFBackgroundRenderer örneği kalıcı bir arka plan servisi değil, tek seferlik bir toplu işçidir — birkaç sayfayı kuyruğa alın, Start'ı çağırın ve son kuyruğa alınan sayfa oluşturulduktan sonra altta yatan işletim sistemi iş parçacığı kendiliğinden sona erer. Execute kuyruğu zaten boşalttıktan sonra aynı örnekte RequestPage'i tekrar çağırmak onu yeniden başlatmaz; yukarıdaki RequestPageWindow'un her çağrıda tek uzun ömürlü bir nesneyi beslemeye çalışmak yerine oluşturucu örneğini neden değiştirdiğinin tam nedeni budur
Delphi'de arka plan iş parçacığından bir TBitmap'e dokunmak güvenli mi?
Belirli bir bit eşlem örneği üzerinde her seferinde yalnızca bir iş parçacığı çalıştığı sürece, bir arka plan iş parçacığından bir TBitmap'e dokunmak HotPDF'in tasarımında güvenlidir ve THPDFBackgroundRenderer bu sınırı çağırana bırakmak yerine kendisi uygular. Execute her sayfayı belgenin kendi oluşturma kilidi içinde oluşturur; bu, her RenderLoadedPageToBitmapCached çağrısının ve yerleşik PrefetchLoadedPages önceden getiricisinin zaten paylaştığı aynı kritik bölümdür; bu yüzden belirli bir sayfa için gerçek GDI çizimi her seferinde tam olarak bir iş parçacığında gerçekleşir ve o belgenin başka bir oluşturmasıyla asla örtüşmez. Ortaya çıkan bit eşlem, THPDFBackgroundRenderer'ın asla doğrudan bir çağırana yayınlamadığı işçi-iş-parçacığı-sahipli bir nesnedir
GetCachedBitmap bunun yerine tamamen yeni bir TBitmap ayırır ve onu oluşturucunun kendi ayrı kilidi altında Assign çağırır; bu yüzden kopya, Execute o önbellek yuvasını altından değiştirmekten engellendiği sırada her zaman gerçekleşir — çağıran iş parçacığı piksel verisi alır, asla orijinal tanıtıcıyı almaz. Bu ayrım aynı zamanda, THPDFBackgroundRenderer veya PrefetchLoadedPages üzerinden geçmeden HotPDF'in oluşturma fonksiyonlarını doğrudan çağıran özel bir oluşturma iş parçacığı yuvarlamaya direnmenin nedenidir: aynı yüklenmiş belgenin paylaşılan önbellekleri ve nesne grafiği üzerinde yarışan iki oluşturma, HotPDF'in dahili kilitlemesinin önlemek için var olduğu tam olarak senaryodur ve arka plan oluşturucu sınıfı, bunu yeniden uygulamak yerine size bedavaya kilitleme verir
Bu, HotPDF'in yerleşik sayfa önceden getirmesinden nasıl farklı?
PrefetchLoadedPages ve THPDFBackgroundRenderer ilişkili ancak farklı sorunları çözer: PrefetchLoadedPages, bir sayfa aralığı verildiğinde, çağıranın oluşturması veya yönetmesi gereken bir kuyruk nesnesi olmadan, o tüm komşuluğu kendi işçi iş parçacığında otomatik olarak paylaşılan belge önbelleğine oluşturur. THPDFBackgroundRenderer bu otomasyonu kontrol karşılığında takas eder — çağıran tam olarak hangi sayfa dizinlerinin önemli olduğuna ve hangi sırayla olduğuna karar verir ve yerleşik önceden getiricinin başka yerde ısıttığı aralığa dokunmadan hâlâ kuyrukta olanları iptal edebilir. Her ikisi de aynı oluşturma kilidinden geçer; bu yüzden bir görüntüleyici, sıradan sonraki-birkaç-sayfa durumu için PrefetchLoadedPages'i çalıştırabilir ve yalnızca kullanıcının az önce tıkladığı bir sayfaya doğrudan atlayan bir küçük resim şeridi gibi o desenin dışında bir şey ortaya çıktığında THPDFBackgroundRenderer'a başvurabilir
begin
// PrefetchLoadedPages takes a 1-based "start-end" range string, while
// RequestPage below stays 0-based like every other loaded-page index.
Pdf.PrefetchLoadedPages(Format('%d-%d', [CenterPage + 1, CenterPage + 5]), 150);
// Reach for THPDFBackgroundRenderer only for a page outside that
// window, such as a thumbnail the user just clicked.
FRenderer := THPDFBackgroundRenderer.Create(Pdf, 150);
FRenderer.RequestPage(ClickedThumbnailPage);
FRenderer.Start;
end;
Üretim koduna taşımaya değer iki yaşam döngüsü detayı vardır. RenderLoadedPageToBitmapCached'in arkasındaki belge geneli önbellek, varsayılan olarak sekiz sayfa olan RenderCacheCapacity ile sınırlıdır ve dolduğunda en uzun süredir kullanılmayan girdiyi tahliye eder; ancak bir THPDFBackgroundRenderer örneğinin kendi sonuç listesinin böyle bir sınırı yoktur — örnek kendisi serbest bırakılana kadar, o örnek üzerinden şimdiye kadar istenen her farklı sayfa dizini için bir bit eşlem tutar; bu yüzden yüksek DPI'de tüm bir kaydırma oturumu boyunca canlı tutulan bir oluşturucu, memnuniyetle geçilen her sayfa için tam çözünürlüklü bir bit eşlem biriktirir. HotPDF ayrıca, bir belge yüklenmeden önce kendi önceden getiricisini iptal ettiği veya kendini yok ettiği şekilde çağıran tarafından oluşturulmuş bir oluşturucuyu otomatik olarak iptal etmez, çünkü bir THPDFBackgroundRenderer örneği işaret ettiği THotPDF nesnesinde asla kayıtlı değildir — bu yüzden çağıran kod, o belgeyi yeniden yüklemeden veya serbest bırakmadan önce ona karşı inşa edilmiş her oluşturucuyu iptal etmeli ve serbest bırakmalıdır; HotPDF'in dahili olarak PrefetchLoadedPages'e uyguladığı aynı sıralama disiplini
THPDFBackgroundRenderer, HotPDF'in MVC görüntüleyici mimarisinin arkasındaki yüklenmiş belge cephesinin bir parçasıdır ve kaydırılan belgenin kendisi ilk etapta rastgele yüklenemeyecek kadar büyük olduğunda büyük PDF'ler için Doğrudan Dosya API'si'ndeki dosya düzeyi iş akışlarıyla doğal olarak eşleşir. Burada anlatılan arka plan oluşturma, istek kuyrukları ve oluşturma önbelleği, hepsi Delphi ve C++Builder için standart HotPDF Bileşeni'nin bir parçasıdır