PDA

View Full Version : حرفه ای: ارتباط با SQL Server در Thread



masoode
دوشنبه 23 فروردین 1395, 12:46 عصر
سلام
من در برنامه ای مرتباً در حال اینسرت کردن دیتا در دیتابیس هستم. از SQL Server استفاده میکنم. در زمان اینسرت های پیاپی UI فریز میشود (یا در بهترین شرایط مرتب نشانگر ماوس ساعت شنی میشود!). می خواهم برای جلوگیری از این مشکل روتینی که در دیتابیس INSERT میکند را به ترد جداگانه منتقل کنم.
اگر با استفاده از Synchronize این کار را انجام بدهم که عملا هیچ سودی ندارد. به فکرم رسید که TADOQuery را به صورت داینامیک در ترد ایجاد کنم اما با مشکل مواجه شدم


procedure MThread.Execute;
var
ADOQInsert:TADOQuery;
EQuery:String;
begin
while SQLQueue.Count<1 do sleep(1);
EQuery:=SQLQueue.Strings[0];
SQLQueue.Delete(0);
try
ADOQInsert.Create(Self);
ADOQInsert.Connection:=Form1.ADOConnection1;
ADOQInsert.Close;
ADOQInsert.SQL.Clear;
ADOQInsert.SQL.Add(EQuery);
ADOQInsert.ExecSQL;
ADOQInsert.Free;
except
end;
end;


در این خط خطا می دهد ADOQInsert.Create(Self); چونکه Self در تِرٍد وجود ندارد!!!!
چه کار می توانم انجام دهم؟

Mask
چهارشنبه 25 فروردین 1395, 10:20 صبح
نه. هنوز هستیم
کدتون کلی ایراد داره.
1- ادو کانکشن رو هم در ترد بسازید
2- نوع create کردنتون اشتباهه.
3-پارامترهای مورد نیاز یه کامپوننتی که به صورت رانتایم میسازید رو باید با دقت وارد کنید.
4-از اشیا و یا کامپوننتهای روی فرم در ترد استفاده نکنید.
5- برای ساخت کامپوننتهای Ado بصورت رانتیم حتما باید از CoInitialize و CoUninitialize استفاده کنید.
و...

Mahmood_M
چهارشنبه 25 فروردین 1395, 14:00 عصر
برای استفاده از Com Object ها مثل ADO باید از CoInitialize در شروع کار و CoUnInitialize در انتهای کار استفاده کنید
هر Thread باید یک Connection مخصوص خودش رو داشته باشه، نمی تونید Connection مربوط به Query رو برابر ADOConnection ای که روی فرم هست قرار بدید
متد Create رو در داخل Try اجرا نکنید، مثال :


Query := TADOQuery.Create(Nil);
try
...
try
Query.ExecSQL;
except
on E:Exception do
begin
...
end;
end
finally
...
Query.Active := False;
Query.Free;
Connection.Disconnect;
Connection.Free;
...
end;


در داخل حلقه ی While و در ابتدای اجرای حلقه Terminate شدن Thread رو چک کنید و اگر Terminate شده بود از حلقه خارج بشید و تا کار Thread به پایان برسه


در این خط خطا می دهد ADOQInsert.Create(Self); چونکه Self در تِرٍد وجود ندارد!!!!
چه کار می توانم انجام دهم؟
مقداری که به عنوان پارامتر Owner در متد Create قرار میدید وظیفه Free کردن Query ساخته شده رو بر عهده داره، داخل Thread برای ساختن اشیا مقدار Nil رو به متد Create بدید و در پایان کار Thread خودتون اشیا ساخته شده رو Free کنید

در کد شما با هر بار اجرای حلقه، Query یک بار ساخته میشه بهتره متد Create رو برای کلاس Thread بازنویسی کنید و ADOConnection و ADOQuery رو در داخل اون Create کنید، در داخل Thread عملیات مورد نظر رو انجام بدید و در پایان کار حلقه Connection و Query ساخته شده رو Free کنید
برای SQLQueue هم یک متغیر از نوع StringList مثلا در Private مربوط به Thread تعریف کنید و در متد Create که بازنویسی کردید، اون رو با لیستی که دارید پر کنید و در OnExecute هم از همون متغیر Thread به عنوان لیست دستورات استفاده کنید
تا جایی که امکان داره سعی کنید از درون Thread به متغیرها و Object ها و ... مربوط به Thread دیگه دسترسی نداشته باشید،حتی اگر عناصر Visual نباشند !

در اینجا (http://delphi.about.com/od/kbthread/a/query_threading.htm) یک نمونه ساده قرار داره، بررسی کنید، اگر مشکلی وجود داشت بگید تا یک نمونه قرار بدم

masoode
پنج شنبه 26 فروردین 1395, 05:31 صبح
ظاهرا مهمترین سوتی من این خطه




ADOQInsert.Create(Self);



این طوری باید اصلاح بشه


ADOQInsert:=TADOQuery.Create(nil);

و برای استفاده از CoInitialize باید یونیت ActiveX را هم به Uses اضافه کنیم

masoode
پنج شنبه 26 فروردین 1395, 05:58 صبح
با راهنمایی دوستان مشکلم حل شد. این کد تکمیل شده ام، شاید به درد یکی دیگه هم بخوره


procedure MThread.Execute;
var
ADOQInsert:TADOQuery;
EQuery:String;
begin
inherited;
CoInitialize(nil) ; //CoInitialize was not called
while (SQLQueue.Count>0) and (not Terminated)do begin
try
ADOQInsert:=TADOQuery.Create(nil);
ADOQInsert.ConnectionString:=ConnStr;
ADOQInsert.CursorLocation := clUseServer;
ADOQInsert.LockType := ltReadOnly;
ADOQInsert.CursorType := ctOpenForwardOnly;
ADOQInsert.Close;
ADOQInsert.SQL.Clear;
EQuery:=SQLQueue.Strings[0];
SQLQueue.Delete(0);
ADOQInsert.SQL.Add(EQuery);
ADOQInsert.ExecSQL;
ADOQInsert.Free;
except
WriteLog('Error: Can not Execute query"'+EQuery+'"','');
end;
end;
CoUninitialize();
end;

masoode
پنج شنبه 26 فروردین 1395, 06:45 صبح
یک سوال دیگه برام پیش آمد.
با توجه به روتین executeی که دیدید، اگر SQLQueue خالی شد ترد بسته می شود و احتمالا اگر درست متوجه شده باشم حافظه اش هم Free می شود. در برنامه من زمانی اتفاق می افتد که لیست SQLQueue کامل خالی می شود. آیا برای اینکه دوباره ترد ساخته شود باید MyThread:=MThread.Create(True); را بنویسم؟
مسلماٌ اگر MyThread هنوز Free نشده باشد مشکل پیش می آید. موقعی که دوباره لازم است من ترد را بسازم چطور می توانم تشخیص دهم که ترد قبلی هنوز وجود دارد؟

Mahmood_M
پنج شنبه 26 فروردین 1395, 09:57 صبح
یک سوال دیگه برام پیش آمد.
با توجه به روتین executeی که دیدید، اگر SQLQueue خالی شد ترد بسته می شود و احتمالا اگر درست متوجه شده باشم حافظه اش هم Free می شود. در برنامه من زمانی اتفاق می افتد که لیست SQLQueue کامل خالی می شود. آیا برای اینکه دوباره ترد ساخته شود باید MyThread:=MThread.Create(True); را بنویسم؟
مسلماٌ اگر MyThread هنوز Free نشده باشد مشکل پیش می آید. موقعی که دوباره لازم است من ترد را بسازم چطور می توانم تشخیص دهم که ترد قبلی هنوز وجود دارد؟
باید متد Create مربوط به Thread رو بازنویسی کنید، در OnCreate جدید ابتدا Thread رو به صورت Suspend شده Create کنید و بعد خاصیت FreeOnTerminate اون رو برابر True قرار بدید
در این صورت بعد از پایان کار Execute ، ترد ساخته شده Free میشه، مثال :


constructor TMyThread.Create(ResultHandle : THandle);
begin
inherited Create(True);
FreeOnTerminate := True;

FResultHandle := ResultHandle;
end;


در این صورت برای شروع کار Thread می تونید به این صورت عمل کنید :

MyThread := TMThread.Create(Handle);
MyThread.Start;

Create کردن و Free کردن یک Object درون try Except به اون صورت صحیح نیست، الان اگر در اجرای دستور SQL خطایی اتفاق بیافته Query شما Free نمیشه، به این صورت بنویسید :


procedure MThread.Execute;
var
ADOQInsert:TADOQuery;
EQuery:String;
begin
inherited;
CoInitialize(nil) ; //CoInitialize was not called

ADOQInsert := TADOQuery.Create(nil);
try
while (SQLQueue.Count > 0) and (not Terminated) do
begin
try
ADOQInsert.ConnectionString:= ConnStr;
ADOQInsert.CursorLocation := clUseServer;
ADOQInsert.LockType := ltReadOnly;
ADOQInsert.CursorType := ctOpenForwardOnly;
ADOQInsert.Close;
ADOQInsert.SQL.Clear;
EQuery := SQLQueue.Strings[0];
SQLQueue.Delete(0);
ADOQInsert.SQL.Add(EQuery);
ADOQInsert.ExecSQL;
except
on E:Exception do
begin
WriteLog('Error on Query Execution :' + #13 + 'SQL : "' + EQuery + '"' + #13 + 'Error : "' + E.Message + '"', '');
end;
end;
end;
finally
ADOQInsert.Free;
end;

CoUninitialize();
end;


برای اطلاع از پایان کار Thread بهترین راه استفاده از Message هاست، به این صورت که در انتهای کار Thread با SendMessage با PostMessage مثلا به Handle فرم اصلی پیغام بفرستید و پایان کار Thread رو اطلاع بدید، Handle فرم رو هم می تونید توی متد Create بازنویسی شده به عنوان ورودی دریافت کنید و در متغیری که مثلا در Private مربوط به Thread تعریف شده ذخیره کنید و در پایان کار Execute به اون Handle ذخیره شده پیغام رو بفرستید ( مثل همون ResultHandle که در متد Create نوشتم )
در این صورت نیازی نیست که حتما متغیری از نوع Thread بسازید و اون رو برابر با Thread ساخته شده قرار بدید، می تونید به صورت زیر هم Thread رو بسازید و Start کنید و منتظر پیغام پایان کار Thread بمونید :

TMThread.Create(Handle).Start;

masoode
دوشنبه 06 اردیبهشت 1395, 13:45 عصر
except
on E:Exception do
begin
WriteLog('Error on Query Execution :' + #13 + 'SQL : "' + EQuery + '"' + #13 + 'Error : "' + E.Message + '"', '');
end;

برای من دوباره یک سوال پیش آمد. احتمالا استفاده از تابع WriteLog که در ترد اصلی برنامه قرار دارد در اینجا دچار مشکل می کند! این تابع در همه جای برنامه استفاده شده است چه روشی پیشنهاد می کنید برای ثیت log ؟

Mahmood_M
دوشنبه 06 اردیبهشت 1395, 15:18 عصر
برای من دوباره یک سوال پیش آمد. احتمالا استفاده از تابع WriteLog که در ترد اصلی برنامه قرار دارد در اینجا دچار مشکل می کند! این تابع در همه جای برنامه استفاده شده است چه روشی پیشنهاد می کنید برای ثیت log ؟
لطفا سئوالتون رو در یک تاپیک جدا مطرح کنید