Bir arka plan iş parçacığı 40.000 satırlık bir raporu dışa aktarırken kullanıcı arabirimi iş parçacığı tek bir hücreyi ayarlıyordu ve diske inen dosya, var olmuş hiçbir çalışma kitabıyla eşleşmiyordu. HotXLS, bu hata sınıfını lxWorkbookView.pas içinde yönetir; burada IXLSWorkbookViewCore O(1) okuma kiraları ve hızlı başarısız yazma koruyucuları verir: bir kira açıkken her değişiklik giriş noktası yazmak yerine özel durum yükseltir
Yığın izi olmadan gelen hata
Bir çalışma kitabını okumak asla tek bir atomik işlem değildir. Bir rapor gezintisi, saniyelere yayılmış on binlerce ayrı hücre okumasıdır ve ikisinin arasına inen tek bir SetValue, gezintinin geri kalanının gördüğü şeyi değiştirmek için yeterlidir. Klasik motor bunu somutlaştırır: TXLSCellRef.SetValue, paylaşılan bir dizge girişini düşürmek için FSST.Remove çağrısı yapabilir, FValueType değerini sıfırlayabilir ve bir formül önbellek durumunu geçersiz kılabilir; üstelik başka bir iş parçacığı tam olarak o yapıların başvurularını çözmenin ortasındayken. O anda hiçbir şey çökmez. Alt toplamları tutmayan bir rapor ya da sessizce artık başka bir yeri gösteren bir dizge dizinini okuyan bir dışa aktarım elde edersiniz
HotXLS bunu bilinçli olarak yazarları bekleterek çözmez. Bir okuyucu bir çalışma kitabını birkaç saniye tutabilir ve bir VCL uygulamasında yazan çoğunlukla ana iş parçacığındaki bir kullanıcı arabirimi geri çağrımı ya da olay işleyicisidir — o iş parçacığını bir arka plan dışa aktarımı bitene kadar engellemek, düzenlemeyi başarısız kılmaktan daha kötü bir sonuçtur. Bu yüzden eşgüdüm çekirdeği, açık bir kiraya karşı yazma denendiği anda, tek bir alana bile dokunulmadan EXLSWorkbookWriteGuardUnavailable hatasını yükseltir ve çağıran düzenlemeyi kuyruğa mı alacağına, yeniden mi deneyeceğine yoksa kullanıcıya mı söyleyeceğine karar verir. Kuyruğa alınan çatışmalar değil, hızlı başarısız olanlar
Bir çalışma kitabını iki iş parçacığından okumak güvenli midir?
Evet, yeter ki her iki okuyucu da bir kira tutsun ve hiç kimse yazmasın. IXLSWorkbookViewCore.AcquireReadLease bir TCriticalSection alır, bir sayacı artırır, geçerli neslin anlık görüntüsünü alır ve bir IXLSWorkbookReadLease döndürür — çalışma kitabı bin hücre ya da bir milyon hücre taşısa da sabit zaman. İstenen sayıda kira bir arada bulunur, herhangi bir sırada bırakılabilirler ve her biri çekirdeği kendi arabirim başvurusuyla canlı tutar; bu yüzden onu oluşturan nesneden uzun yaşayan bir kira, sarkan bir işaretçi yerine güvenlidir. Her iki motor da katılır: lxHandle.pas içindeki TXLSWorkbook ve lxHandleX.pas içindeki TXLSXWorkbook, kurucularının her birinde bir çekirdek oluşturur ve _AcquireReadLease ile _AcquireWriteGuard yordamlarını sunar
Kira kadar önemli olan şey, kiranın okuma yoluna eklemediği şeydir. Kritik bölüm kira alınmasını, kira bırakılmasını ve yazma işlemi sınırlarını kapsar — başka hiçbir şeyi. Sıradan hücre başı okuma hiçbir zaman bir kilide, bir izleneğe ya da atomik bir sayaca girmez; bu yüzden kira tutmak tüm tarama için bir alma ve bir bırakmaya mal olur, hücre başına bir tane değil. Paralel XLSX ayrıştırma ve bellek ayırıcı çalışmasının ardındaki aynı tasarım içgüdüsüdür: eşgüdümün bedelini sınırda ödeyin, asla iç döngüde değil. Simetrik kural da geçerlidir — AcquireReadLease, WriteDepth sıfırdan farklı olduğunda EXLSWorkbookReadLeaseUnavailable hatasını yükseltir; bu yüzden yazma işleminin içinden, yazan iş parçacığında bile bir kira açamazsınız
uses
lxHandle, lxWorkbookView;
procedure TReportThread.Execute;
var
Lease: IXLSWorkbookReadLease;
Sheet: TXLSWorksheet;
Row: Integer;
Total: Double;
begin
// Yazma sürüyorsa EXLSWorkbookReadLeaseUnavailable hatasını yükseltir
Lease := FWorkbook._AcquireReadLease;
Sheet := FWorkbook.Sheets[1];
Total := 0;
for Row := 1 to 50000 do
Total := Total + Sheet.Cells[Row, 3].Value;
FTotal := Total;
// Kira burada kapsamdan çıkar: başvuru sayısı sıfıra düşer,
// ReleaseReadLease çalışır ve yazarlar yeniden mümkün hale gelir
end;
Yazma koruyucusu gerçekte nerede durur?
En alttaki değişebilir katmanda, asla üstündeki kolaylık APIlerinde değil. _AcquireWriteGuard, TXLSCellRef.SetValue işlevinin içinden çağrılır; bu da ona bağlanan her ortak yolun — Range.Value, çalışma sayfası metin ataması, hücre hücre kopyalama, yapıştırma — bir kez kapılanması anlamına gelir; her sarmalayıcının, gelecekteki bir sarmalayıcının unutacağı bir denetimi yinelemesi yerine. Kapsam bilinçli olarak geniştir: çekirdeği getiren toplu iş itibarıyla lxHandle.pas içinde 55 koruyucu alımı ve lxHandleX.pas içinde 37 tane
Kapılanan yüzey hücre değerlerini ve hücre biçimlendirmesini, TXLSWorkbook.Open, kopyalama ve yapıştırmayı, tanımlı adları (Add, yeniden adlandırma, RefersTo, Visible, IsMacro, Comment, Delete), Name, Zoom, Visible, StandardHeight, FreezePanes, Protect ve Activate gibi çalışma sayfası meta verilerini, sayfa yapılandırmasını, sayfa sonlarını ve Calculate işlevini kapsar. Yerleşim tüm meselenin özüdür: koruyucu ilk alan yazılmadan önce alınır, sonradan bir bildirim kancasıyla doğrulanmaz; bu yüzden reddedilen bir değişiklik modeli bayt bayt aynı bırakır. Regresyon paketi tam olarak bunu doğrular: reddedilen her çağrıdan sonra sayfa adı, yakınlaştırma, görünürlük, standart yükseklik, kenar boşlukları, yönelim ve sayfa sonu sayılarını yeniden okur. Yükleme yolları aynı işlemi bir katman aşağıda görür; ZIP okuma kapısı paket biçimleri için eşzamanlı açmayı eşgüdümler
procedure TXLSWorksheet.Activate;
var
WriteGuard: IXLSWorkbookWriteGuard;
begin
// İlk alana dokunulmadan önce alınır, asla sonradan değil
WriteGuard := FWorkbook._AcquireWriteGuard;
if not FSelected then
begin
FWorkbook.FWorkSheets.Deselect;
FSelected := True;
end;
FWorkbook.FWorkSheets.FActiveSheet := Self;
// Nesli yalnızca tamamlanmış en dıştaki koruyucu ilerletir
WriteGuard.Complete;
end;
İç içe bir yazma nesli neden yalnızca bir kez ilerletir?
Çünkü bir yazma işlemi her koruyucu tarafından ayrı ayrı değil, bir iş parçacığındaki en dıştaki koruyucu tarafından tanımlanır. Çekirdek, iş parçacığı kimliği, derinlik ve tamamlanma bayrağı tutan iş parçacığı başına bir yazar durumu tutar. Aynı iş parçacığındaki ikinci bir AcquireWriteGuard o durumu bulur ve yeni bir işlem oluşturmak yerine Depth değerini artırır; FGeneration değeri ancak Depth sıfıra geri düştüğünde — en dıştaki koruyucu Complete olarak işaretlenmiş olarak — ilerler. Calculate ya da Open gibi üst düzey bir işlemin altında on korumalı ilkel çağırıp yine de tek bir değişiklik olarak kaydolmasını sağlayan budur. İç Complete çağrıları kaydedilir ama sayacı kendi başlarına hareket ettirmezler ve koruyucular muhasebeyi bozmadan sıra dışı bırakılabilir
Başarısızlık yönü aynı ölçüde açıktır. Bir koruyucu Complete olmadan bırakılırsa — özel durumun arabirim başvurusunu sarmalamasının sıradan sonucu — nesil ilerlemez, çünkü yazma işlemi hiçbir zaman başarı bildirmemiştir. Bunun ne anlama geldiği konusunda açık görüşlü olun: HotXLS kısmi düzenlemeyi geri almaz. Sayaç hiçbir başarılı işlemin tamamlanmadığını kaydeder; bir önbelleğin gereksinim duyduğu sinyal tam olarak budur, ancak modeli önceki durumuna geri getirmek, başvuru sayımlı bir koruyucunun sizin için yapabileceği bir şey değildir. İşlem ortası bir hata çalışma kitabını gönderemeyeceğiniz bir biçimde bırakabiliyorsa, bellek içi nesneye güvenmek yerine kaynak dosyayı saklayın ve onu yeniden açın
Nesil sayacı size ne kazandırır?
Tarama olmadan ucuz bayatlık algılama. Generation, 1 değerinde başlayan ve taşmada 0 değerini atlayan bir UInt64 değeridir; bu yüzden 0 çekirdeğin hiçbir zaman vermediği bir değerdir ve güvenilir bir “hiç gözlemlenmedi” nöbetçisi olarak çalışır. İki değişmez onu kullanılabilir kılar: herhangi bir okuma kirası varken nesil hareket edemez ve her başarılı yazma işlemi onu tam olarak bir kez artırır. Böylece IXLSWorkbookReadLease.Generation, kiranın tüm ömrü boyunca sabit kalan bir anlık görüntüdür ve IXLSWorkbookWriteGuard.StartGeneration, bir yazara işlemi açıldığında modelin nasıl göründüğünü söyler. Bir kılavuz, bir yazdırma önizlemesi ya da türetilmiş bir dizin, satırları farklamak yerine tek bir tamsayıyı karşılaştırabilir
var
Lease: IXLSWorkbookReadLease;
begin
Lease := FWorkbook._AcquireReadLease;
if Lease.Generation <> FCachedGeneration then
begin
FCachedGeneration := Lease.Generation;
RebuildRowHeightCache;
end;
PaintVisibleRows;
// FCachedGeneration 0 ile başlar; çekirdeğin hiç vermediği bir değerdir,
// bu yüzden ilk geçiş her zaman yeniden kurar
end;
Bu eşgüdümün vaat etmediği şeyler
Üç sınırı açıkça belirtmekte yarar var, çünkü tersini varsaymak mekanizmanın kötüye kullanılma yoludur. Birincisi, yazma koruyucusu yazarlar arasında karşılıklı dışlama değildir: çekirdek okuyucuları yazarlara karşı dışlar ve iki farklı iş parçacığı aynı anda her biri bir yazma koruyucusu tutabilir, her biri nesli bağımsız olarak ilerletir — bir regresyon sınaması tam olarak bu davranışı doğrular. Kendi yazar iş parçacıklarınızı serileştirmek hâlâ sizin işinizdir. İkincisi, buradaki hiçbir şey bir dosya kilidi ya da süreçler arası bir kilit değildir; tek bir süreç içindeki iş parçacıklarını tek bir çalışma kitabı örneğine karşı eşgüdümler ve aynı .xlsx dosyasını açan iki süreç birbirinden habersizdir. Üçüncüsü, garanti yalnızca gerçekten kira alan çağıranlara ulaşır — kirasız bir okuma hâlâ kilitlenmemiş sıcak yoldan geçer; bu yol hızlıdır ve tamamen korumasızdır. Bu bir eşgüdüm çekirdeğidir, işlemsel bir veritabanı değildir
Bu sınırlar içinde kullanıldığında küçük, dürüst bir ilkeldir: dokuz adanmış regresyon sınaması, Win32 ve Win64 üzerinde geçen 1.328 sınamalık bir pakette birden çok okuyucuyu, her iki çatışma yönünü, yeniden girişliliği, sıra dışı bırakmayı, iptal edilen işlemleri ve iş parçacıkları arası okuma/yazma ile yazma/yazma yarışlarını kapsar. Onu çökmeye karşı güvenli aşamalı geçici dosya kaydetme yoluyla eşleştirin; bir arka plan dışa aktarımı uçtan uca hakkında akıl yürütebileceğiniz bir şey haline gelir — okurken tutarlı, yazarken atomik. Okuma kiraları, yazma koruyucuları ve nesil sayacı, Delphi ve C++Builder için HotXLS Delphi Component ürününde klasik ve paket motorlarının bir parçası olarak sunulur; etkinleştirmek için hiçbir yapılandırma gerekmez