私が取り組んでいるDelphiアプリケーションは、1秒、場合によっては2秒遅延する必要があります。ベストプラクティスを使用してこの遅延をプログラムしたいと思います。 stackoverflowでDelphiのSleep()メソッドに関するエントリを読んで、次の2つのコメントを見つけました。
私はこの格言で生きています。「Sleep()を使用する必要性を感じたら、それは間違っています。」 – Nick Hodges、2012年3月12日1:36
@nick確かに。私の同等物は「睡眠が解決策である問題はありません」です。 – David Heffernan 12年12月12日8:04
Sleep()の呼び出しを回避するためのこのアドバイスに応えて、DelphiのTTimerクラスとTEventクラスの使用に関する私の理解とともに、次のプロトタイプをプログラミングしました。私の質問は:
type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
public
EventManager: TEvent;
end;
TDoSomething = class(TThread)
public
procedure Execute; override;
procedure Delay;
end;
var
Form1: TForm1;
Something: TDoSomething;
implementation
{$R *.dfm}
procedure TDoSomething.Execute;
var
i: integer;
begin
FreeOnTerminate := true;
Form1.Timer1.Interval := 2000; // 2 second interval for a 2 second delay
Form1.EventManager := TEvent.Create;
for i := 1 to 10 do
begin
Delay;
writeln(TimeToStr(GetTime));
end;
FreeAndNil(Form1.EventManager);
end;
procedure TDoSomething.Delay;
begin
// Use a TTimer in concert with an instance of TEvent to implement a delay.
Form1.Timer1.Enabled := true;
Form1.EventManager.ResetEvent;
Form1.EventManager.WaitFor(INFINITE);
Form1.Timer1.Enabled := false;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Something := TDoSomething.Create;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
// Time is up. End the delay.
EventManager.SetEvent;
end;
順番に質問をする:
はい(ただし「いいえ」も-下を参照)。
「正しい方法」は、特定の要件と解決される問題によって異なります。これにはniversal Truthはなく、他の方法であなたに言っている人は何かを(言い換えれば)販売しようとしています。
場合によっては、イベントを待機することが適切な遅延メカニズムです。他の場合ではありません。
上記参照:答えisはい。ただし、この2番目の質問はSleep()が常にであり、必要に応じて説明されているように適切な方法ではないことを前提としているため、単に意味をなさない上記の#1の答えでは、必ずしもそうではありません。
Sleep()は、allシナリオで遅延をプログラムするための最良または最も適切な方法ではない可能性がありますが、それが最も実用的であり、重大な欠点はありません。
なぜ人々は睡眠を避けているのか
Sleep()は、特定の期間が経過するまで中断できない無条件の遅延であるため、潜在的な問題です。代替の遅延メカニズムは通常、まったく同じことを実現しますが、唯一の違いは、単なる時間の経過以外に、実行を再開するためのいくつかの代替メカニズムが存在することです。
イベントの待機は、イベントが発生する(または破棄される)まで遅延しますor特定の時間が経過します。
Mutexを待機すると、mutexが取得(または破棄)されるまで遅延が発生しますor特定の期間が経過しました。
等.
つまり、一部の遅延メカニズムは中断可能です。 Sleep()ではありません。しかし、他のメカニズムが間違っている場合でも、重大な問題が発生する可能性があり、特定するのがはるかに難しい場合もあります。
この場合、Event.WaitFor()の問題
問題のプロトタイプは、anyメカニズムを使用して、コードの残りの部分がその特定のアプローチと互換性があります:
Form1.Timer1.Enabled := true;
Form1.EventManager.ResetEvent;
Form1.EventManager.WaitFor(INFINITE);
このコードがメインスレッドで実行される場合、Timer1は発生しません。
問題のプロトタイプはこれをスレッドで実行するので、この特定の問題は発生しませんが、プロトタイプはこのスレッドの関与の結果として別の問題を引き起こすため、潜在的な可能性を調査する価値があります。
[〜#〜] infinite [〜#〜]イベントでタイムアウトを待機するWaitFor()をイベントに指定すると、そのイベントが発生するまでスレッドの実行が一時停止されます。 TTimerコンポーネントは、Windowsメッセージベースのタイマーメカニズムを使用します。タイマーが経過すると、メッセージキューにWM_TIMERメッセージが提供されます。 WM_TIMERメッセージが発生するには、アプリケーションがメッセージキューを処理している必要があります。
別のスレッドにコールバックを提供するWindowsタイマーを作成することもできます。これは、この(明らかに人工的な)ケースでより適切なアプローチになる可能性があります。ただし、これはVCL TTimerコンポーネントが提供する機能ではありません(少なくともXE4以降、XE2を使用していることに注意してください)。
問題#1
上記のように、WM_TIMERメッセージは、アプリケーションがメッセージキューを処理することに依存しています。 2秒のタイマーを指定しましたが、アプリケーションプロセスが他の作業でビジー状態の場合、そのメッセージが処理されるまでに2秒よりはるかに長い時間がかかる可能性があります。
ここで言及する価値があるのは、Sleep()も不正確になる可能性があることです。これにより、指定された期間少なくともスレッドが中断されることが保証されます。指定された遅延を正確に保証しません。
問題#2
プロトタイプは、タイマーとイベントを使用して2秒間遅延するメカニズムを考案し、Sleep()への単純な呼び出しで達成できたのとほぼ同じ結果を達成します。
これと単純なSleep()呼び出しの唯一の違いは、待機しているイベントが破棄されるとスレッドも再開することです。
ただし、遅延に続いてさらに処理が行われる現実の状況では、これが正しく処理されない場合、それ自体が重大な問題になる可能性があります。プロトタイプでは、この不測の事態はまったく考慮されていません。この単純なケースでも、イベントが破棄された場合、スレッドが無効にしようとするTimer1も破棄される可能性が高くなります。 Access Violationは、そのタイマーを無効にしようとすると、結果としてスレッドで発生する可能性があります。
警告開発者
Sleep()の使用を独断的に回避することは、すべてのスレッド同期メカニズム(遅延は1つだけです)およびオペレーティングシステム自体が機能する方法を正しく理解するための代替手段ではありません。必要に応じて展開できます。
実際、プロトタイプの場合、Sleep()は間違いなく「より良い」ソリューションを提供します(信頼性が重要な指標である場合)。この手法の単純さにより、コードは2秒後に再開されるためです。 (当面の問題に関して)過度に複雑な手法で不注意を待つ落とし穴に陥ることなく。
そうは言っても、このプロトタイプは明らかに不自然な例です。
私の経験では、Sleep()が最適なソリューションである実際の状況はfew非常に少なくなりますが、多くの場合、最もエラーが発生しにくい最も簡単です。しかし、私は決して言わないでしょう。
シナリオ:いくつかの連続したアクションを、それらの間の特定の遅延で実行したいとします。
これは遅延をプログラムする適切な方法ですか?
より良い方法があると思います。以下を参照してください。
答えが「はい」の場合、なぜこれがSleep()の呼び出しよりも優れているのですか?
メインスレッドでスリープすることは悪い考えです。Windowsのパラダイムはイベントドリブンです。つまり、アクションに基づいてタスクを実行し、次にシステムに処理を任せます。システムからの重要なメッセージを停止させることができるため(シャットダウンの場合など)、スレッドでのスリープも悪いです。
オプションは次のとおりです。
ステートマシンのようにメインスレッドのタイマーからアクションを処理します。状態を追跡し、タイマーイベントが発生したときにこの特定の状態を表すアクションを実行します。これは、タイマーイベントごとに短時間で終了するコードで機能します。
一連のアクションをスレッドに入れます。イベントタイムアウトをタイマーとして使用して、スリープ呼び出しでスレッドがフリーズしないようにします。多くの場合、これらのタイプのアクションはI/Oバウンドであり、組み込みのタイムアウトで関数を呼び出します。これらの場合、タイムアウト数は自然な遅延として機能します。これは私のすべての通信ライブラリが構築される方法です。
後者の代替の例:
procedure StartActions(const ShutdownEvent: TSimpleEvent);
begin
TThread.CreateAnonymousThread(
procedure
var
waitResult: TWaitResult;
i: Integer;
begin
i := 0;
repeat
if not Assigned(ShutdownEvent) then
break;
waitResult := ShutdownEvent.WaitFor(2000);
if (waitResult = wrTimeOut) then
begin
// Do your stuff
// case i of
// 0: ;
// 1: ;
// end;
Inc(i);
if (i = 10) then
break;
end
else
break; // Abort actions if process shutdown
until Application.Terminated;
end
).Start;
end;
あれを呼べ:
var
SE: TSimpleEvent;
...
SE := TSimpleEvent.Create(Nil,False,False,'');
StartActions(SE);
アクションを中止するには(プログラムのシャットダウンまたは手動による中止の場合):
SE.SetEvent;
...
FreeAndNil(SE);
これにより、TSimpleEvent
によってタイミングが駆動される匿名スレッドが作成されます。一連のアクションの準備ができると、スレッドは自動的に破棄されます。 「グローバル」イベントオブジェクトは、手動またはプログラムのシャットダウン中にアクションを中止するために使用できます。
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Memo1: TMemo;
Timer1: TTimer;
RichEdit1: TRichEdit;
Button1: TButton;
CheckBox1: TCheckBox;
procedure Delay(TickTime : Integer);
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Past: longint;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
delay(180000);
beep;
end;
procedure TForm1.Delay(TickTime: Integer);
begin
Past := GetTickCount;
repeat
application.ProcessMessages;
Until (GetTickCount - Past) >= longint(TickTime);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if checkbox1.Checked=true then Past:=0;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
end;
end.