メインスレッドからワーカスレッドを開始し、
ワーカースレッドの終了を待ってからメインスレッドで
String型文字列操作を行うとエラーが出る現象に遭遇しました。
エラーの内容は、以下の通りです。
Exception EAccessViolation in module test.exe at 000036CC.
Access violation at address 004036CC in module 'test.exe'. Read of address 41414141.
エラーが出ないように修正はできるのですが、エラー原因が分からないので、
本当に直っているのかが不安です。
原因をご存知の方、何か情報をお持ちの方、お助け願います。
エラーが出る環境:
Windows XP Professional SP1
Pentium4 2.4GHz
2GB RAM
Delphi6 , Delphi3.1
上記環境で、90%近くエラーが再現します。
他のパソコンではエラーが出なかったりします。
以下に、エラーが出るソースを載せます。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
procedure Th_Exec;
implementation
{$R *.dfm}
type
TThreadFunc = class(TThread)
private
{@1}a: array [0..4096] of Integer;
procedure ThreadFuncEndProc(Sender: TObject);
protected
procedure Execute; override;
public
end;
var
ThreadFuncTerminated: Boolean; //スレッド終了感知フラグ
//スレッド終了時に呼び出されるイベントハンドラ
procedure TThreadFunc.ThreadFuncEndProc(Sender: TObject);
begin
ThreadFuncTerminated := True; //スレッド終了感知フラグTrue
end;
//スレッド実行部
procedure TThreadFunc.Execute;
begin
Sleep(2000);
end;
procedure Th_Exec;
var
ThreadFunc: TThreadFunc;
begin
ThreadFunc := TThreadFunc.Create(True);
{@2} ThreadFunc.FreeOnTerminate := False;
ThreadFunc.OnTerminate := ThreadFunc.ThreadFuncEndProc;
ThreadFuncTerminated := False;
ThreadFunc.Resume;
//スレッドが終了するまで待つ
repeat
Sleep(10);
Application.ProcessMessages;
until ThreadFuncTerminated;
{@3} ThreadFunc.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
S: String;
i: Integer;
begin
try
Th_Exec;
{@4}
S := 'A';
for i:=0 to 40 do
S := S +'A';
MessageDlg(S, mtInformation, [mbOk], 0);
except
MessageDlg('例外捕捉', mtError, [mbOk], 0);
end;
end;
end.
【参考事項】
・[Button1]をクリックすると、'AAAAA'のダイアログとともに、エラーが出る。
・{@2}のFreeOnTerminateをTrueにし、{@3}のFreeを削除すると、エラーが出なくなる。
・{@1}の使っていない領域を削除すると、エラーが出なくなる。
・{@3}の辺りに Sleep(1) を入れると、エラーが出なくなる。
・{@4}の辺りで、String型文字列操作をしなければ、エラーは出ない。
・エラーのRead of address 41414141 は、{@4}の AAAA のところ?
・エラー時、except で例外を捕捉できません。
#長文ですみません
-----------------------------------------------
飯塚 伸二
iizuka...@kf.hitachi-medical.co.jp
-----------------------------------------------
>WaitForを使って、終了を待ってみてはどうでしょう?
WaitForを使うと、メインスレッドが終了を待っている間、
イベントに反応できなくなってしまうので
Application.ProcessMessagesをループで回しています。
なお、エラーが出ないようにする対策方法ではなく、
エラーが発生する原因を知りたいのです。
よろしくお願いします。
-----------------------------------------------
飯塚 伸二
iizuka...@kf.hitachi-medical.co.jp
-----------------------------------------------
説明不足ですみません。
ループはいままで通り、Application.ProcessMessagesを使っておき、
until の後、ThreadFunc.Freeをする前に、WaitForしてみてはどうで
しょう?
という意味でした。
エラーの原因の予想としては、スレッド側インスタンスが完全に処理
を終えていないのに、Freeしてしまっているのでは?
と思っています。
(OnTerminate イベントの直後では、まだサブスレッド側が動いている
のに、Freeしようとしている)
飯塚 伸二 さんは書きました。< Mon, 23 Feb 2009 17:17:01 +0900 >
----------
Tomomi Umezawa um...@procast.co.jp
試しに、プログラムの初期化処理として
IsMultiThread := True;
の一行をどこかに追加してみてください。
スレッドオブジェクトを新規作成した場合など、メモリマネージャがマルチスレッド
対応である必要がある場合には、自動的に IsMultiThread := True; になるばすですが...
Delphi6などでは、IsMultiThread := True; にならないこともあるようですね
--
高橋智宏
飯塚さん:
これ、まずいです。 上記のコードの中に問題があります。
ワーカスレッドの終了時には OnTerminate イベントが発生しますが、
ワーカスレッドはこのイベントハンドラを呼び出した後、何もしないわけでは
ありません。 少なくとも関数呼び出しの巻き戻し(スタックからのポップ)を
したり、ワーカスレッドの使っていたリソースを返したり、いろいろなことを
しなければなりません。 それにもかかわらず、メインスレッドが {@3} で
ThreadFunc を解放してしまったので、ワーカスレッドがエラーを出したのです。
WaitFor を使うか、FreeOnTerminate を True にするのが正しい解決策です。
――――――――――――――――――――――――――――――――――――
株式会社イマジオム 代表取締役 高木太郎
〒316-0024 茨城県 日立市 水木町 1-11-10
電話:0294-28-0147
ファクシミリ:0294-28-0148
電子メール:tarou_...@imageom.co.jp
ホームページ:http://www.imageom.co.jp/
昨夜奇遇にも私もマルチスレッドアプリを作っていたので
分かる範囲で返事いたします。
解決法は何種類かありますが、ソースをいじらない方から解決策を
挙げますと
1.{@3}の部分を
ThreadFunc.DoTerminate;
としてみてください。
エラーの原因は「スレッドを正しく終了させてないのに
Freeしているから」という可能性が非常に高いです。
2.{@2}の部分を
ThreadFunc.FreeOnTerminate := True;
として、{@3}をコメントアウトしてしまう。
どうせ処理が終われば開放するのですから
1.より更にマシだと思われます。
3.以上の解だとスレッドが終わるまで処理を待つので
正直、マルチスレッドにする意味がないです。
Th_Execは
//スレッド実行部
procedure Th_Exec;
var
ThreadFunc: TThreadFunc;
begin
ThreadFunc := TThreadFunc.Create(False);
{@2} ThreadFunc.FreeOnTerminate := True;
ThreadFunc.OnTerminate := ThreadFunc.ThreadFuncEndProc;
end;
とするとスレッド実行中もボタンイベントを受け付けます。
試しに連打してみてください。
スレッドが終わったかどうか確認したいなら
ThreadFuncEndProcを
//スレッド終了時に呼び出されるイベントハンドラ
procedure TThreadFunc.ThreadFuncEndProc(Sender: TObject);
begin
Application.MessageBox('スレッド終了', nil);
end;
とすると、メッセージボックスが出て確認できます。
4.どうしてもスレッド終了まで同期がとりたいなら
上記のTh_Exec処理の最後に
ThreadFunc.Synchronize(ThreadFunc.Execute);
を入れるとExecute処理終了まで待ちます。
マルチスレッドアプリを作っていて
sleepとかwhileとかuntilなどの
待つ処理やループを使いたくなったときは
何か設計を間違っている可能性が高いです。
下記が参考になるかと思います。
http://support.codegear.com/jp/article/35961
では、頑張ってください。
飯塚 伸二(ShinjiIizuka) さんは書きました:
エラー原因は、ワーカスレッドが終了していないのにFree を呼び出して
いたことによって、ワーカスレッド側でエラーとなっていたのですね。
理解いたしました。
梅澤@プロキャスト さん
高橋(智) さん
イマジオムの高木 さん
下田 さん
本当に有難うございました。