(02-05-2018, Saat: 23:32)Bahadir.Alkac Adlı Kullanıcıdan Alıntı: (02-05-2018, Saat: 22:20)Tuğrul HELVACI Adlı Kullanıcıdan Alıntı: İyi akşamlar. Öncelikle kıymetli bilgilerinizi paylaştığınız için teşekkür ederim. Belirtmek isterim ki, ben bir çözüm aramıyorum. Amacım, önemli gördüğüm bu husus hakkında bir bilinç oluşmasını sağlamaya çalışmaktı.
Müsaadeniz ile yazdıklarınızın bir kısmına iştirak etmediğimi belirtmek isterim. "Sonsuza dek bekleme olmaz !" demişsiniz. INFINITE sonsuzluk anlamına gelse de sizin de bileceğiniz gibi esasen 49.7 güne tekabül eder. Bazen bir thread, başka thread'lerin işlerini tamamlamalarını beklemek durumunda kalır.Sırası ile yapılması gereken işler olabilir. Ya da bu tarz senaryolar çoğaltılabilir. Bu durumda, yapılacak işlerin ne kadar süreceği bilinmiyor ise INFINITE ile beklemekten başka bir çare yoktur.
Elbette işlerin bitmesini beklemenin başka yolları da vardır. Örneğin pencereler arası mesajlaşma API'leri (PostMessage, SendMessage vb.) ya da callback mekanizmaları gibi. Ancak, bahse konu kernel senkronizasyon nesnelerinin beklenmesi son derece yaygın bir uygulama alanına sahiptir ve bunun sisteme de faydası olduğu yadsınamaz bir gerçektir. (Bir thread nonsignaled (işaretli olmayan) kernel nesnelerinden birisini beklemeye başladığında aslında işletim sistemi ilgili thread'i pause eder ve başka thread'leri işletmeye devam eder.)
Bu bağlamda, bu tarz bir beklemenin "tasarım hatası" ile ilişkilendirilmesini pek kabul edemiyorum.
Application.ProcessMessages çağrımına neden gıcık olduğunuzu, ona neden bu denli temkinli yaklaştığınızı bilmiyorum ama; Application.ProcessMessages altında yatan API'ler (GetMessage, TranslateMessage, DispatchMessage) olmadan Windows ortamında bir uygulamadan bahsedilemeyeceğini siz de benim kadar bilirsiniz.
Aslında kafayı fazla karıştırmaya gerek yok. Genel geçer çok önemli bir kural var Windows işletim sisteminde programlamada. "Pencere create etmiş her bir thread, ilgili pencerenin mesaj kuyruğunu tüketmelidir".
Bunun istisnası yoktur. Aksi durumda, beklenmedik durumlar (genellikle kilitlenmeler) oluşur. Bu neden ile; ana thread haricinde oluşturduğumuz thread'lerimiz içinde, bir pencere oluşturuyor isek (Form'da bir penceredir) bu pencerenin mesaj döngüsünü kesinlikle tüketmeliyiz.
Bu arada thread'ler içinde gizli biçimlerde pencere create eden API'ler vardır. Bunlara da dikkat etmek gerekir. (Bunların en önemlisi, CoInitialize API'sidir. Sık kullanılır). İçinde pencere oluşturan thread'lerin beklenmesi için en uygun API MsgWaitForMultipleObject/Ex API'sidir.
Thread'ler, uygulama penceresinin mesajlarını tüketen API'leri çağırmak(Application.ProcessMessages, PeekMessage vb) aslında; paralel programlamanın gücünden istifade etmeye çalışmaktır. Yıl 2018 ve biz hâla tek çekirdekli programlar geliştiriyor isek, hatayı biraz da kendimizde aramamız gerekir. Bu nedenler ile yoğun bir şekilde, tüm arkadaşlarımı korkulan multi-thread konseptine yaklaştırmaya gayret ediyorum tüm gücümle. Ancak sebebine anlam veremediğim çok kuvvetli bir direnç ile karşılaşıyorum her daim. Kimbilir belki ben iyi anlatamıyorum, önemine vurgu yapamıyorum. Bilemiyorum.
Birden fazla thread ile çalışmaya başladığınızda uygulamalarınız bir t zamanında çok daha fazla iş yapacak demektir. Programınız bir iş yaparken aynı anda bir başka iş daha yapabilecektir. (Aslında öyle değil tabii. Windows gerçek bir multithreaded işletim sistemi değildir. Sadece simüle eder.) Bunun faydası kime olacak peki ? Elbette programınızı kullanacak kullanıcılara. Amiyane tabir ile yağ gibi akan bir programınız olacak.
Bu konu başlığı altındaki WaitMessage API'si de çağrıldığı thread'in mesaj kuyruğuna bir mesaj ulaşana kadar bekleme işini yapar. İlgili MSDN dökümanlarını inceleyenler göreceklerdir. MsgWaitForMultipleObjects API'si gerçekten de güçlü bir API'dir ve yapabildiklerini görünce; insanın içinde "onu kullanmalıyım" düşünceni oluşturur.
Herneyse, uzunca yazarak gözlerinizi ağrıttı isem affola.
@Tuğrul HELVACI uzun açıklamalarınız için teşekkürler, ama sanırım lafı uzatmayım diye kısa kesmem bazı detayların eksik kalmasına sebep olmuş.
Mesajı yazarken arada "event-driven" programcılıktan da bahsetmiş, ama sonradan mesaj uzun olunca silmiştim Anlayacağınız, eğer event-driven programcılıktan bahsediyorsak gerçekten de Application.ProcessMessages kötü bir metod oluyor, çünkü o zaman event değil, fonksiyon bekliyorsunuz demektir. Bunun en güzel örneği Android işletim sistemi. Event-driven programcılık sayesinde UI bile ayrı bir thread'de çalışıyor ve Google bundan kesinlikle taviz vermiyor (Android işletim sisteminde ShowModal olmamasını sebebi de bu). Application.ProcessMessages aslında ana thread'e yanlış bir sorumluluk yüklemenin getirdiği bir kaçış fonksiyonu. Event-driven programcılıkta, ana thread bir işlem yapılacağı zaman, ilgili thread'i çalıştırıyor ve kendi iç parametrelerini buna göre güncelliyor. İlgili threadden mesaj geldiği zaman (illa mesaj değil, aslında bir event tetiklendiği zaman) gerekli işlemleri yapıyor.
Benim açımdan Application.ProcessMessages'in kabul edilebileceği iki durum var:
1. İlk mesajımda da yazdığım gibi, program yazmak hızlı yapılan bir iş değil. Ama şartlar, müşteri, patron artık herhangi bir sebepten dolayı (itiraf ediyorum, sık sık da üşenmekten) hızlı yazmak gerekiyorsa Application.ProcessMessages bir çok dertten kurtarıyor. Kod kaliteli olmuyor ama zaten hızlı yazıyoruz
2. 3. parti API, SDK, kütüphane kullanıyorsanız işler sizin planladığınız gibi gitmeyebiliyor. Örneğin eskiden Skype API'si Windows mesajları üzerine kuruluydu (hala öyle mi bilmiyorum) ve karmaşık tasarımlarda (plug-in tabanlı ve Skype'in plug-in olarak çalışması gibi) Application.ProcessMessages işi kurtarabiliyor. Burada dikkat edilmesi gereken, asıl uygulamanın Skype referans alınarak tasarlanmamış olması. Yani plug-in'lerin Windows mesajlarını kullanması beklenmedik bir durum ve ciddi bir sürpriz.
Sonuç olarak, event driven programcılık seven birisi olarak Application.ProcessMessages fonksiyonuna özel duygular beslemiş olabilirim.
İyi çalışmalar
İyi günler, @
Bahadir.Alkac bey cevabınıza teşekkür ederim. Üstadım elmalar ile armutları mukayese etmişsiniz. Application.ProcessMessages'in event'ler ile nasıl bir bağlantısı var anlamlandıramadım. Android işletim sisteminin event-driven yapısından bahsetmişsiniz ve eğer yanlış anlamadı isem bu yapı sayesinde GUI'nin ayrı bir thread'de çalıştığını söylemişsiniz. İyi ama Windows'da da GUI ayrı bir thread'de çalışıyor zaten ! Bu thread'e main thread diyoruz. Sanırım bazı kavram karmaşaları yaşıyorsunuz. Windows işletim sisteminde her şey pencereler arası mesajlaşmadan ibarettir. Bu nedenle yapılan hemen hemen her iş bir mesaj ile temsil edilir. Bu mesajlar (WM_PAINT, WM_TIMER, WM_LBUTTONDOWN vb..) işletim sistemi tarafından uygulamanın ana thread'inin mesaj kuyruğuna bırakılır. Ve uygulamanın ana thread'i de mesajları sırası ile alır ve işler. Bu mesaj döngüsü olmadan Windows işletim sisteminde görsel bir uygulama yazılamaz. Hatta servis uygulamasının bile görsel uygulamalara benzeyen bir döngüsü vardır. Şimdi kısaca sizleri biraz derinlere indireyim. Bu sıcak havada serinlik iyi gelecektir eminim. Tüm arkadaşlarım, Delphi'de create ettikleri bir projenin kaynak kodlarına gitsinler. Şuna benzer bir şey ile karşılaşacaklar:
program pDevExControlsDemo;
uses
{$IFDEF EurekaLog}
EMemLeaks,
EResLeaks,
EDialogWinAPIMSClassic,
EDialogWinAPIEurekaLogDetailed,
EDialogWinAPIStepsToReproduce,
EDebugExports,
EDebugJCL,
EFixSafeCallException,
EMapWin32,
EAppVCL,
ExceptionLog7,
{$ENDIF EurekaLog}
Vcl.Forms,
uDevExControlsDemo in 'uDevExControlsDemo.pas' {Form1},
uFindAndReplaceEx in 'uFindAndReplaceEx.pas' {frmFindAndReplaceEx},
uTaskDialog in 'uTaskDialog.pas';
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Peki daha önce hiç merak ettiniz mi (Bahadır bey size değil genele hitap ediyorum);
Application.Run satırından sonra program neden sonlanmıyor ? Programlar yukarıdan aşağıya doğru çalışmazmıydı ? Öyle ise neden uygulama hemen sonlanmıyor ? Bunun cevabı için Application.Run'ın içine bir gidelim:
procedure TApplication.Run;
begin
FRunning := True;
try
{$IF NOT DEFINED(CLR)}
AddExitProc(DoneApplication);
{$ENDIF}
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE:
begin
FInitialMainFormState := wsMinimized;
FMainForm.FWindowState := wsMinimized;
end;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if (FMainForm.FWindowState = wsMinimized) or (FInitialMainFormState = wsMinimized) then
begin
Minimize;
if (FInitialMainFormState = wsMinimized) then
FMainForm.Show;
end else
FMainForm.Visible := True;
// BU DÖNGÜYE DİKKAT !!!!
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
end;
finally
FRunning := False;
end;
end;
Dikkat ettiniz mi, bir
döngü var Şimdi de uygulama sonlanana kadar (Terminated) sürekli çağrılan
HandleMessage metoduna bakalım o ne imiş ?
procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then Idle(Msg);
end;
Peki
ProcessMessage ne imiş ? Bir de ona bakalım:
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
Unicode: Boolean;
MsgExists: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
begin
Unicode := (Msg.hwnd = 0) or IsWindowUnicode(Msg.hwnd);
if Unicode then
MsgExists := PeekMessageW(Msg, 0, 0, 0, PM_REMOVE)
else
MsgExists := PeekMessageA(Msg, 0, 0, 0, PM_REMOVE);
if MsgExists then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsPreProcessMessage(Msg) and not IsHintMsg(Msg) and
not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
if Unicode then
DispatchMessageW(Msg)
else
DispatchMessageA(Msg);
end;
end
else
begin
{$IF DEFINED(CLR)}
if Assigned(FOnShutDown) then FOnShutDown(self);
DoneApplication;
{$ENDIF}
FTerminate := True;
end;
end;
end;
end;
Görüldüğü gibi, uygulama kuyruğunda bir mesaj var ise (
PeekMessage ile bakıldı ama son parametreye dikkat: mesajı
silme !) bu durumda mesaj işleniyor. Aşağılarda
TranslateMessage ve
DispatchMessage API çağrımlarını görebilirsiniz. Buradan da anlaşılabileceği üzere, mesaj döngüsü olmayan bir uygulama(GUI)
çalışamıyor! bile. Evet gördüğünüz gibi Delphi herzamanki hüneri ile çok ince detayları kullanıcılarından gizleyebilmiş. Ama bizler Delphi değil de C/C++ programcısı olsaydık, ya da TForm sınıfı yerine kendi penceremizi oluşturmak durumunda kalsaydık; o durumda
PeekMessage,
GetMessage,
TranslateMessage,
DispatchMessage API'leri ile fazlası ile haşır neşir olacaktık.
Kısacası sizin dediğiniz gibi Application.ProcessMessages "
ana thread'e yanlış bir sorumluluk yüklemekten kaynaklanan bir kaçış metodu"
değil.! O sistemin olmazsa olmazına bir referans yapıyor sadece. Ana thread'iniz işletim sistemine 5 sn. boyunca yanıt vermez ise; işletim sistemi uygulamanızı "Not Responding" konumuna alır. Buna Ghosting de diyorlar, pencereniz Ghost Window (Hayalet) olur. İşte bu olmasın diye, bazen programcılar; ana thread'in mesaj kuyruğuna gelen mesajları da işlemeye izin veren Application.ProcessMessages metodunu çağırırlar. Bu; işi başından aşkın bir vezne memurunun, veznenin önünde biriken evrakların üstünden bir kısmını alıp masaya koymasına, dolayısı ile evrakların devrilmesine mani olmasına benzetilebilir. Evraklar devrilir ise, vezne memuru yerinden kalkmak ve evrakları toplamak zorunda kalır. Böyle bir durumda üzerinde çalıştığı işin de durması gerekir. Tabii başka örnekleri sizler tahayül edebilirsiniz.
Şu cümlenizi ise hiç anlayamadım:
"
Event-driven programcılıkta, ana thread bir işlem yapılacağı zaman, ilgili thread'i çalıştırıyor ve kendi iç parametrelerini buna göre güncelliyor. İlgili threadden mesaj geldiği zaman (illa mesaj değil, aslında bir event tetiklendiği zaman) gerekli işlemleri yapıyor."
Event kısaca bir
metod pointer'dır. Metod pointer ise bir kod bloğunun hafızadaki başlangıcını gösteren bir işaretçi(pointer) dan başka bir şey değildir. Metod pointer'ın çağrılması CPU'nun ilgili pointer adresinden itibaren kodu işletmesinden başka bir şey değildir. Kısacası event'lerin thread kavramı ile bir ilgisi yoktur.
Saygı ve sevgilerim ile...