View Full Version : سوال: اختلاف بین تایمر ها در دلفی
sajioo
یک شنبه 14 دی 1393, 21:48 عصر
سلام دوستان گلم
سوالم را اینطور مطرح می کنم
بنده بر روی فرمم برای مثال 10 تایمر دارم که هر تایمر یک کار خاصی انجام میده.
وظیفه ی تایمر 1 اینه که از 1 تا 1000 با فاصله ی زمانی 1 ثانیه شمارش انجام بده و وقتی به 1000 رسید به کاربر پیام بده که 1000 ثانیه است که در برنامه فعال است.
طی یک سری بررسی ها که داشتم دیدم این تایم متفاوت هستش.
برای مثال وقتی طی این 1000 ثانیه من با برنامه فعال هستم و در حال کار کردن اگر قرار باشه با توجه به ساعت ویندوز برنامه من ساعت 10:20:20 به من پیام بده بعضی وقتا 10:20:30 یا حتی بعضی وقتا 10:20:55 ثانیه به من پیام داده .این عقب افتادگی زمانی برای چیه ؟ آیا دلیل خاصی داره؟
سوال دوم بنده هم در خصوص تایمر هست باز
برای مثال من فرم 4 برنامه خود را گفتم هروقت show شد تایمر 2 فعال بشه و بعد از 10 ثانیه فرم 4 رو hide کنه. این تایمر اکثر مواقع درست کار میکنه اما جدا بعضی وقتا عمل نمیکنه .
حالا دقیقا نمیدونم چرا ؟ ولی خودم تست کردم و مطمئن شدم مثلا از 100 بار 1 بار این عمل نمیکنه. واقعا دلیل این موضوع چیه ؟ یا اگر نکته خاصی هست بنده خیلی دوست دارم بشونم.
با تشکر
یوسف زالی
دوشنبه 15 دی 1393, 04:25 صبح
سلام.
خطای درحد یک ثانیه معموله، و این موضوع برمی گرده به این که میلی ثانیه شما دقیقا الان چنده، مثلا ممکنه 999 باشه و نتیجه نهایی با اختلاف یک میلی ثانیه یک ثانیه گرفته خواهد شد.
از طرفی تایمر وقتی باهاش رو تعداد بالا کار می کنید خطاش رو نشون می ده، حالا چطور، ببینید:
شما فرض کن تایمری داری که قراره هر یک ثانبه چیزی رو چک کنه، تایمر براساس اصول ترد (thread) در ویندوز، بنا شده و suspend و resume می شه، ویندوز هم بنا به صلاحدید خودش ممکنه دقیقا در لحظه ای که مد نظر شماست، ترد رو ساسپند نکنه و یکم اختلاف داشته باشه، مثلا ترد با اولویت بالاتر در صف داشته باشه. حالا اگر این اختلاف برای هر دور ساسپند شدن، دو هزارم ثانیه باشه، در هزار دور می شه دو ثانیه.
اما راه درست، اینه که به جای شمارش تعداد و ضرب اون در interval ، و در نتیجه خطا ضرب در تعداد شمارش، بیایید هر بار نتیجه نهایی رو مقایسه کنید با زمانی که قرار بوده باشه، مثل این:
procedure TForm1.FormCreate(Sender: TObject);
begin
{old fasion
Caption := TimeToStr(Time);
Timer1.Tag := 0;
Timer1.Interval := 1000;
Timer1.Enabled := true;
}
Timer1.Tag := GetTickCount {or high resolution APIs} + 1000 {interval} * 100 {period};
Timer1.Interval := 100;
Timer1.Enabled := true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
{old fasion
Timer1.Tag := Timer1.Tag +1;
if Timer1.Tag = 100 then
ShowMessage(TimeToStr(Time));
}
if Timer1.Tag <= GetTickCount {or high resolution APIs} then
ShowMessage('Horaaaaaa');
end;
اما این که کلا تایمری ران نشه، باید با دقت بیشتری بررسی بشه، شاید ران می شه ولی نه دقیقا سر تایم!
Felony
شنبه 20 دی 1393, 09:19 صبح
تایمر براساس اصول ترد (thread) در ویندوز، بنا شده و suspend و resume می شه، ویندوز هم بنا به صلاحدید خودش ممکنه دقیقا در لحظه ای که مد نظر شماست، ترد رو ساسپند نکنه و یکم اختلاف داشته باشه، مثلا ترد با اولویت بالاتر در صف داشته باشه
تایمر بر اساس اصول ترد بنا نشده :)
تایمرها چند نوع مختلف در ویندوز دارن که نوع معمول که در اکثر زبان های برنامه نویسی منجمله Delphi استفاده میشه نوع message base ش هست ؛ این نوع تایمر ها با تابع SetTimer (http://msdn.microsoft.com/en-us/library/windows/desktop/ms644906%28v=vs.85%29.aspx)خودشون رو در ویندوز رجیستر میکنن و ویندوز وظیفه داره در زمان تائین شده برای Interval که در تابع SetTimer پاس داده شده به هندل پنجره ی پاس داده شده در همین تابع پبغام WM_Timer رو ارسال کنه :
procedure TTimer.UpdateTimer;
begin
KillTimer(FWindowHandle, 1);
if (FInterval <> 0) and FEnabled and Assigned(FOnTimer) then
if SetTimer(FWindowHandle, 1, FInterval, nil) = 0 then
raise EOutOfResources.Create(SNoTimers);
end;
وظیفه پنجره یاد شده این هست که پیغام WM_Timer رو هندل کنه و وقتی دریافتش کرد عملیات مناسب باهاش رو انجام بده :
procedure TTimer.WndProc(var Msg: TMessage);
begin
with Msg do
if Msg = WM_TIMER then
try
Timer;
except
Application.HandleException(Self);
end
else
Result := DefWindowProc(FWindowHandle, Msg, wParam, lParam);
end;
در دلفی پیاده سازی تایمر به این صورت هست که یه پنجره مخفی در زمان ساخته شدن ترد ایجاد میشه و هندل این پنجره به تابع SetTimer ارسال میشه تا پیغام های Tick این تایمر از طرف ویندوز به این هندل ارسال بشه ( خط آخر ) :
constructor TTimer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FEnabled := True;
FInterval := 1000;
FWindowHandle := AllocateHWnd(WndProc);
end;
در ویندوز در هر ثانیه هزاران پیغام بین ویندوز و پنجره ها تبادل میشه و پیغام های زیادی هم وجود دارن که Broadcast میشن یعنی مقصد مشخصی ندارن و به همه ی پنجره های دارای هندل Valid ارسال میشن ، خوب پس Message Handler با تعداد پیغام های بسیار زیادی رو به رو هست که در صف پردازشیش ( Message Queue ) قرار دارند و باید همرو پردازش کنه و در صورت نیاز بهشون پاسخ بده ، در همین پیاده سازی تایمر دلفی در کد مربوط به override کردن تابع WndProc که در بالا قرار دادم اگر به خط آخر دقت کنید میبینید که وقتی پیغامی رسید و نوعش WM_Timer نبود دلفی تابع DefWindowProc رو صدا میزنه (API) که وظیفش صدا زدن default handler مربوطه هست تا پیغام به صورت پیش فرض پردازش بشه .
یکی از عوامل کندی تایمر دلفی به همین دلیل هست ؛ و اینکه میگید تایمرها بر اساس ترد در ویندوز پیاده سازی شدن و suspend و resume میشن درست نیست .
دیگر عامل کندی این تایمر ها این هست که به صورت User mode پیاده سازی شدن و ویندوز تضمینی بابت ارسال پیغام WM_Timer سر زمان تعیین شده نمیده و ممکنه چند میلی ثانیه اختلاف وجود داشته باشه .
اگر زمان بندی خیلی دقیقی در سناریوتون مورده نیازه باید از High Resolution Timer ها استفاده کنید ، مثل : waitable تایمرها و queue تایمرها و multimedia تایمرها
این مقاله هم نحوه استفادشون رو کامل توضیح داده : http://www.codeproject.com/Articles/1236/Timers-Tutorial
موفق باشید .
یوسف زالی
شنبه 20 دی 1393, 10:40 صبح
به به بسیار خوش آمدید دوست عزیز!
اینکه میگید تایمرها بر اساس ترد در ویندوز پیاده سازی شدن و suspend و resume میشن درست نیست .
ممنون از اصلاحیه. یادمه قبل تر ها تو SO دیده بودم، پس نیازه که مجدد مطالبش رو ببینم.
در کل این طور استفاده کردن از تایمر درست نیست، که بیاییم و زمان های کوتاه رو در تعداد بالا بشمریم.
در ترد ها هم همین مطلب هست، یعنی نمی شه توقع داشت که دقیقا سر زمانی که ما بهش گفتیم، اون هم در تعدادهای بالا، زمان بندی ما درست از آب بیرون بیاد.
بیشتر باش فلونی جان. :متفکر:
BORHAN TEC
شنبه 20 دی 1393, 22:17 عصر
با سلام،
یادمه قبل تر ها تو SO دیده بودم، پس نیازه که مجدد مطالبش رو ببینم.
من هم قبلاً مطلبی رو در این خصوص خونده بودم و تا جایی که یادمه جایی میگفت که priority تایمر پیش فرض دلفی از نوع Idle هست! :متفکر: ولی الان بعد از رسیدن به این مطلب فهمیدم که قضیه فرق میکنه : :چشمک:
http://stackoverflow.com/questions/11957954/ttimer-not-firing
خطاب به sajoo (http://barnamenevis.org/member.php?61492-sajioo):
برای راحتی کار میشه به جای تایمر پیش فرض از تایمرهای دیگری نظیر TJvTimer از مجموعه JVCL استفاده کرد که خاصیتی به نام ThreadPriority داره که میشه مقادیر مختلفی مثل tpIdle و tpNormal و tpHighest و ... رو به اون اختصاص داد. البته باید خاصیت Threaded اون رو هم به True ست کنید.
Delphi Coder
شنبه 20 دی 1393, 23:07 عصر
مولتی مدیا تایمر به مراتب خیلی دقیقتر هست و به احتمال زیاد کارتونو راه میندازه. البته من دقیق نبودن مولتی مدیا تایمر رو تجربه کردم (حدود 15 الی 30 ثانیه اختلاف در 2 ساعت تجربه ای هست که من طی چندین تست داشتم) ولی فکر میکنم اختلافش برای کارهای این تیپی قابل اغماض هست. اگر دنبال دقت بیشتری هستید Waitable و Queue Timers رو امتحان کنید من خودم تست نکردم اگر شما اینکار رو کردید لطف کنید و نتیجه رو به ما هم اطلاع بدید.
hamedjim
سه شنبه 07 بهمن 1393, 11:01 صبح
دوستان سلام.
من هم برای ارسال یک سری دیتا از طریق پورت سریال نیاز دارم تا دقیقا هر 100 میلی ثانیه یک سری کد رو ارسال کنم. به روشی که دوستان توضیح دادند از GetTickCount به این شکل استفاده کردم:
در هنگام ایجاد فرم، مقدار GetTickCount رو دریافت میکنیم:
LastTicks := GetTickCount;
LabelTick.Caption:='';
LabelTime.Caption:='';
Timer1.Enabled:=False;
Timer1.Interval:=5;
Interval رو هم برای تایمر مقداری کمتر از حد مورد نیاز قرار دادم تا روتین مربوط به وقفه تایمر سریعتر چک بشه. کد زیربرنامه تایمر رو هم به این شکل نوشتم:
procedure TForm1.Timer1Timer(Sender: TObject);
var
CurTicks, Elapsed: DWORD;
begin
CurTicks :=GetTickCount; {هر 49.7 روز صفر می شود}
if CurTicks >= LastTicks then
Elapsed := CurTicks - LastTicks
else
Elapsed := ($FFFFFFFF - LastTicks) + CurTicks;
if Elapsed >= Interval then
begin
LastTicks := CurTicks;
LabelTick.Caption:=FormatInClock(CurTicks);
LabelTime.Caption:=TimeToStr(Time);
{.
.
.
کدهای برنامه
.
.
.}
end;
end;
توضیح اینکه وقتی Elapsed به اندازه Interval برسه می تونیم کدهایی که برای وقفه نیاز داریم رو قرار بدیم. Interval مقدار ثابت (Const) مورد نظرمون برای وقفه هست.
میشه نظرتون رو در این مورد اعلام کنید؟
نمونه برنامه نوشته شده با XE2 رو هم به طور کامل ضمیمه میکنم:
127947
یوسف زالی
سه شنبه 07 بهمن 1393, 23:44 عصر
دلیل وجود این کدها چیه؟
if CurTicks >= LastTicks then
Elapsed := CurTicks - LastTicks
else
Elapsed := ($FFFFFFFF - LastTicks) + CurTicks;
hamedjim
چهارشنبه 08 بهمن 1393, 16:15 عصر
تا اونجا که من فهمیدم توسط تابع GetTickCount زمان سیستم رو میشه دریافت کرد. این زمان هر 49.7 روز صفر میشه.
زمان دوبار، یکی زمان ایجاد فرم و یکی در وقفه تایمر گرفته میشه و اختلافش میشه مدت زمانی که از زمان ایجاد فرم تا زمان ورود به وقفه تایمر اتفاق افتاده. و میشه توسط یه تابع ساده مقدارش رو بر حسب روز و ساعت و دقیقه و ثانیه و میلی ثانیه بدست آورد. قسمت else هم برای زمانی هست که نمونهگیری زمانی اول قبل از صفر شدن GetTickCount و نمونهگیری دوم بعد از صفر شدنش اتفاق بیفته.
اما بعد از امتحان کردن این روش، به این نتیجه رسیدم که خیلی سطحی به این برنامه نگاه کردم. این روش مناسبی برای بدست آوردن یک زمان دقیق نیست.
شما لطف می کنید و بگید من چطور می تونم دقیقا هر x میلی ثانیه به روتین وقفه برم و عملیاتی رو انجام بدم؟
hamedjim
چهارشنبه 08 بهمن 1393, 19:16 عصر
خب فکر میکنم به یه نتایجی رسیدم.
اینبار با استفاده از MultiMedia Timer یه برنامه نوشتم که نتایج قابل قبولی رو بهم داد.
وقتی تابع timeSetEvent اجرا میشه، MultiMedia Timer اجرا میشه و با توجه به مقادیر آرگومانهای تابع، به TimeCallBack میره.
uses
;MMSystem
...
فرمان اجرای تایمر:
mmResult := TimeSetEvent(10, 0, @TimeCallBack, 0, TIME_PERIODIC);
...
دستورات مورد نظر در زیربرنامه TimeCallBack نوشته می شود.
procedure TimeCallBack(TimerID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal;
نمونه برنامه:
127995
نتیجه برنامه بعد از 83 دقیقه و 21 ثانیه:
127999
poua1351
شنبه 06 بهمن 1403, 13:24 عصر
سلام
تابع TimeGetTime در دلفی 2010 برای کپی کردن و نمایش زمان کپی اشکال میگیره لطفا راهنمایی کنید
یوسف زالی
یک شنبه 07 بهمن 1403, 10:11 صبح
سلام. چه اروری؟ کدتون چیه؟ الان چطور باید ما بفهمیم چی نوشتید و چی ارور گرفتید؟
poua1351
یک شنبه 07 بهمن 1403, 11:36 صبح
سلام در قسمت نمایش زمان در ProgressBar هنگام کامپایل کردن از تابع TimeGetTime ارور نامشخص بودن تابع را میدهد. این تابع TimeGetTime را چطور باید معرفی کنم
poua1351
یک شنبه 07 بهمن 1403, 11:38 صبح
تابع نمایش زمان به هنگام کپی را از همین سایت گرفتم. اگر مفهوم را نرساندم ارور را برای شما ارسال میکنم
یوسف زالی
یک شنبه 07 بهمن 1403, 15:43 عصر
uses DateUtils رو امتحان کنید
poua1351
دوشنبه 08 بهمن 1403, 13:55 عصر
سلام خیلی سپاسگزارم انجام شد ارور برنامه برطرف شد.
دلفــي
شنبه 13 بهمن 1403, 10:18 صبح
سلام دوستان گلم
سوالم را اینطور مطرح می کنم
بنده بر روی فرمم برای مثال 10 تایمر دارم که هر تایمر یک کار خاصی انجام میده.
وظیفه ی تایمر 1 اینه که از 1 تا 1000 با فاصله ی زمانی 1 ثانیه شمارش انجام بده و وقتی به 1000 رسید به کاربر پیام بده که 1000 ثانیه است که در برنامه فعال است.
طی یک سری بررسی ها که داشتم دیدم این تایم متفاوت هستش.
برای مثال وقتی طی این 1000 ثانیه من با برنامه فعال هستم و در حال کار کردن اگر قرار باشه با توجه به ساعت ویندوز برنامه من ساعت 10:20:20 به من پیام بده بعضی وقتا 10:20:30 یا حتی بعضی وقتا 10:20:55 ثانیه به من پیام داده .این عقب افتادگی زمانی برای چیه ؟ آیا دلیل خاصی داره؟
سوال دوم بنده هم در خصوص تایمر هست باز
برای مثال من فرم 4 برنامه خود را گفتم هروقت show شد تایمر 2 فعال بشه و بعد از 10 ثانیه فرم 4 رو hide کنه. این تایمر اکثر مواقع درست کار میکنه اما جدا بعضی وقتا عمل نمیکنه .
حالا دقیقا نمیدونم چرا ؟ ولی خودم تست کردم و مطمئن شدم مثلا از 100 بار 1 بار این عمل نمیکنه. واقعا دلیل این موضوع چیه ؟ یا اگر نکته خاصی هست بنده خیلی دوست دارم بشونم.
با تشکر
اینم یه تست بزن:
var
Form1: TForm1;
StartTime, EndTime, NowTime: TDateTime;
implementation
{$R *.dfm}
uses System.DateUtils;
procedure TForm1.FormCreate(Sender: TObject);
begin
StartTime := Now;
EndTime := IncSecond(StartTime, 1000);
Timer1.Interval := 1000;
Timer1.Enabled := True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
NowTime := Now;
if NowTime >= EndTime then
begin
Timer1.Enabled := False;
ShowMessage('1000 seconds have passed in the program.');
end;
end;
vBulletin® v4.2.5, Copyright ©2000-1404, Jelsoft Enterprises Ltd.