PDA

View Full Version : ارسال هم زمان نتایج دو یا چند Thread ، به Main Thread



مهران رسا
چهارشنبه 31 فروردین 1390, 14:11 عصر
سلام،

در برنامه ام دو Thread مجزا وجود داره . هر دو کار مشابهی انجام میدند و نهایتاً باید نتایج کار رو در لیست باکسی که در Thread اصلی قرار گرفته درج کنند. با اینکه دستورات مربوط به دسترسی به Listbox ، سینکرونایز هستند اما وقتی هر دو به صورت هم زمان بخوان نتایج رو در لیست اضافه کنند برنامه Freeze میشه. راه حل شما چیه ؟ ضمن اینکه حتماً اصراری برای استفاده از این روش نیست . ممنون میشم راهنمایی کنید اگه روش بهتری برای ارسال هم زمان نتایج دو یا چند Thread ، به Main Thread وجود داشته داشته باشه .

Felony
چهارشنبه 31 فروردین 1390, 14:22 عصر
تو Thread هات با تابع SendMessage یا PostMessage مقداری که باید در ListBox درج بشه رو همراه با پیغام خاصی ( مثلا WM_AddList ) به هندل فرمی که توش ListBox قرار داره ارسال میکنی بعد برای اون فرم یک Message Handler مینویسی و توش بررسی میکنی اگر پیغام مورد نظر ( در اینجا WM_AddList ) رسیده بود آیتم رو از پیغام استخراج میکنی و درجش میکنی ، با این کار وظیفه درج آیتم به Main Thread سپرده میشه .

مهران رسا
چهارشنبه 31 فروردین 1390, 14:27 عصر
خیلی ممنون . حالا اگه مستقیماً کنترل های GUI مد نظر نباشند و بخوایم اطلاعات رو در یک متغیر عمومی در Thread اصلی قرار بدیم کار به شکل هست ؟

Felony
چهارشنبه 31 فروردین 1390, 14:54 عصر
بستگی به عملیات مورد نظرتون داره ، نیاز دارید تا اطلاعات به صورت Realtime به متغییر عمومیتون منتقل بشه یا مهم نیست ؟
اگر نیاز هست که یکی از راه حل های خوب همون روش قبلی هست ولی اگر Realtime بودن انتقال اطلاعات براتون مهم نیست یهتره تا پایان عملیات Thread ها صبر کنید و پس از پایان اطلاعات رو مثلا اگر داخل یک StringList ذخیره شدن به اون StringList عمومی Assign کنید .

مهران رسا
چهارشنبه 31 فروردین 1390, 15:27 عصر
بله ، اطلاعات قراره به صورت Real-time ارسال بشن. چون میخوام سرعت کار افزایش پیدا کنه از چند Thread استفاده میکنم که باید هم زمان نتایج رو اعلام کنن. پس راهش استفاده از SendMessage هست ؟ در این مورد میشه یه مثال بزنید ؟

Felony
چهارشنبه 31 فروردین 1390, 15:44 عصر
تو بخش نمونه کدها قبلا یه نمونه نوشته بودم که به همین روش فایل ها رو جست و جو میکرد ( پست شماره 30 ) : http://barnamenevis.org/showthread.php?209792-%D8%B3%D9%88%D8%B1%D8%B3%D9%87%D8%A7%D9%8A-%D9%86%D9%85%D9%88%D9%86%D9%87-%D8%A2%D9%85%D9%88%D8%B2%D8%B4%D9%8A&p=1068925&viewfull=1#post1068925

مهران رسا
چهارشنبه 31 فروردین 1390, 15:59 عصر
مرسی ؛ آقای تاجیک با استفاده از TStringList چطوری اینکار رو انجام بدم ؟ حواسم نبود که برای شروع عملیات باید اطلاعات همه Thread ها دریافت شده باشه . پس اگر نخوایم اطلاعات به صورت Real-time دریافت بشند ، علاوه بر تعریف یک آرایه داینامیک یا TStringList برای هر Thread در نهایت چطوری از خاتمه کار ترد ها با خبر بشم و آرایه ها رو Collect کنیم ؟

مهران رسا
چهارشنبه 31 فروردین 1390, 16:17 عصر
ضمناً مورد دیگه در زمان استفاده از SendMessage اینه که حجم اطلاعات ارسالی زیاد هستش . میشه از طریق Param ارسالش کرد ؟ مشکلی پیش نمیاد ؟

Felony
چهارشنبه 31 فروردین 1390, 17:38 عصر
مرسی ؛ آقای تاجیک با استفاده از TStringList چطوری اینکار رو انجام بدم ؟ حواسم نبود که برای شروع عملیات باید اطلاعات همه Thread ها دریافت شده باشه . پس اگر نخوایم اطلاعات به صورت Real-time دریافت بشند ، علاوه بر تعریف یک آرایه داینامیک یا TStringList برای هر Thread در نهایت چطوری از خاتمه کار ترد ها با خبر بشم و آرایه ها رو Collect کنیم ؟
یک تابع Collector بنویسید و بعد از اتمام کار هر Thread از داخل همون ترد تابع Collector رو صدا بزن ، مثلا من تابع زیر رو برای Collect کردن اطلاعات به صورت Public در کلاس TForm برنامم تعریف کردم :

type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
CollectedList: TStringList;
public
{ Public declarations }
procedure Collect(aStrList: TStringList);
end;

همونطور که میبینید یه شئ هم از کلاس TStringList به صورت خصوصی تعریف کردم که نتیجه Collect رو میخوام داخلش نگهداری کنم ، تابع Collector رو به صورت زیر نوشتم :

procedure TForm1.Collect(aStrList: TStringList);
var
i: Integer;
begin
if not Assigned(CollectedList) then
CollectedList:= TStringList.Create;

CollectedList.AddStrings(aStrList);
end;

حالا کافیه تو تردهام بعد از اتمام کار این تابع رو صدا بزنم ؛

ترد 1 :
procedure TThread1.Execute;
var
aStrList: TStringList;
i: Integer;
begin
aStrList:= TStringList.Create;
try
for i:= 1 to 1000 do
aStrList.Add(IntToStr(i));
finally
Form1.Collect(aStrList);
end;
end;

ترد 2 :
procedure TThread2.Execute;
var
aStrList: TStringList;
i: Integer;
begin
aStrList:= TStringList.Create;
try
for i:= 1 to 1000 do
aStrList.Add(IntToStr(i));
finally
Form1.Collect(aStrList);
end;
end;
حالا بعد از اتمام کار تردها به صورت خودکار تابع Collect رو صدا میزنن و اطلاعات اون ها داخل Main Thread جمع آوری میشه و شما از طریق CollectList بهشون دسترسی دارید .

- یادتون نره که شئ aStrList رو در هر دو Thread بعد از پایان کار آزاد کنید ، همچنین CollectList رو جایی که کارتون باهاش تموم شد آزاد کنید .

- aStrList رو داخل بلوک Try ... Finally خود تردها آزاد نکردم چون داخل بلوک تابع Collect صدا زده شده و کار این تابع بسته به تعداد آیتم ها طول میکشه و اگر داخل بلوک Try ... Finally شئ رو آزاد کنید ترد بلافاصله میره سراغ آزاد سازی aStrList و Collect کردن آیتم ها ناتمام میمونه .

- این کار رو میشه خیلی بهینه تر و فنی تر انجام داد ، مثلا یک Thread جدا برای Collect کردن نوشت و داخلش یک صف قرار داد که به ترتیب هر Thread که کارش تموم شد داخل صف قرار بگیره و به ترتیب FIFO اطلاعاتشون Collect بشه .

خلاصه این کلیت کار بود و ممکنه اشکالاتی داشته باشه که باید کمی وقت صرف کنید و روند مورد نظر خودتون رو کاملتر پیاده کنید .


ضمناً مورد دیگه در زمان استفاده از SendMessage اینه که حجم اطلاعات ارسالی زیاد هستش . میشه از طریق Param ارسالش کرد ؟ مشکلی پیش نمیاد ؟
در مورد مشکل پیش اومدن اطلاعی ندارم و جایی هم ندیدم که توصیه ای کرده باشه ولی SendMessage و PostMessage برای جا به جایی اطلاعات این چنینی طراحی نشده ، بهتره اطلاعات رو داخل یک شئ StringList یا ... ذخیره کنید و آدرس اشاره گرش رو به عنوان پارامتر ارسال کنید و تو Message Handler از طریق اشاره گرش بهش دسترسی پیدا کنید .

مهران رسا
چهارشنبه 31 فروردین 1390, 18:13 عصر
خیلی ممنون . اما وقتی کار دو Thread هم زمان تموم بشه ، برای دسترسی به TForm1.Collect به مشکل بر نمیخورن ؟

Felony
چهارشنبه 31 فروردین 1390, 18:41 عصر
نه چه مشکلی ؟
اولا شما با تابع Collect بیرون از تردها کاری ندارید ؛ دوما اگر هم کاری داشته باشید تابع Collect به عنوان متد کلاس TForm1 نوشته شده چه ارتباطی با تردها داره ؟!
برای دسترسی به مقادیر جمع آوری شده هم با شئ CollectedList کار دارید که در بخش Public کلاس TFrom1 تعریف شده ، البته برای دسترسی به داده های این شئ بهتره اون رو به صورت خصوصی تعریف کنید و براش یک متد بنویسید مثل متد Collect که برای اضافه کردن به CollectedList نوشتم .

SAASTN
چهارشنبه 31 فروردین 1390, 21:12 عصر
ممنون از ایده های جالب آقای تاجیک ولی یه چندتا نکته برام موند که با اجازه آقا مهران بپرسم:

برای اینکه حافظه اضافی از برنامه مصرف نشه و سرعت بالا باشه ورودی تابع Collector رو به صورت by Reference در نظر گرفتم ، حالا کافیه تو تردهام بعد از اتمام کار این تابع رو صدا بزنم ؛
آقا سوال اینکه مگه اگر StringList بصورت Call By Value ارسال بشه چه میزان دیتا ردوبدل میشه. من تصورم این بود که خود متغیر مربوط به اشیا در دلفی تنها یه اشاره گر به محل allocate شده برای شیئ هستند. که اگه اینطور باشه در عمل تفاوتی در ارسال یک شئ بصورت فراخوانی با مقدار یا فراخوانی با آدرس از نظر حافظه ای وجود نداره. در اولی مقدار یک اشاره گر به محل تخصیص ارسال میشه و در دومی یک اشاره گر به آدرس خود متغیر شیئی.

aStrList رو داخل بلوک Try ... Finally خود تردها آزاد نکردم چون داخل بلوک تابع Collect صدا زده شده و کار این تابع بسته به تعداد آیتم ها طول میکشه و اگر داخل بلوک Try ... Finally شئ رو آزاد کنید ترد بلافاصله میره سراغ آزاد سازی aStrList و Collect کردن آیتم ها ناتمام میمونه .
اینجا هم یک نکته برام هست، مثلا اگه فرض کنیم روال Execute مربوط به TThread1 بصورت زیر نوشته بشه:
procedure TThread1.Execute;
var
aStrList: TStringList;
i: Integer;
begin
aStrList:= TStringList.Create;
try
for i:= 1 to 1000 do
aStrList.Add(IntToStr(i));
finally
Form1.Collect(aStrList);
end;
aStrList.Free;
end;
خوب در اینجا منظورتون اینه که بعد از اجرای خط:

Form1.Collect(aStrList);
چون داخل Thread1 هستیم پس ترد منتظر اتمام روال Collect در ترد اصلی نمی مونه و بلافاصله aStrList.Free رو شروع می کنه؟
اگر به این صورت هم باشه اونموقع آزاد کردن aStrList در داخل تابع Collect چه صورتی داره؟ به هر صورت این متغیر یک متغیر محلی از روال Execute هست و تا اون موقع ممکنه اجرای Execute در Thread1 به پایان رسیده باشه.
از طرفی جای دیگه ای هم به این متغیر دسترسی نداریم، مگر اینکه aStrList بصورت یک Property یا Field در خود کلاس TThread1 تعریف بشه و در Destructor همون کلاس آزادش کنیم؟

Felony
چهارشنبه 31 فروردین 1390, 22:13 عصر
آقا سوال اینکه مگه اگر StringList بصورت Call By Value ارسال بشه چه میزان دیتا ردوبدل میشه. من تصورم این بود که خود متغیر مربوط به اشیا در دلفی تنها یه اشاره گر به محل allocate شده برای شیئ هستند. که اگه اینطور باشه در عمل تفاوتی در ارسال یک شئ بصورت فراخوانی با مقدار یا فراخوانی با آدرس از نظر حافظه ای وجود نداره. در اولی مقدار یک اشاره گر به محل تخصیص ارسال میشه و در دومی یک اشاره گر به آدرس خود متغیر شیئی.
در این مورد حواسم نبود که ورودی پارامتر رو StringList در نظر گرفتم ، نه فرقی نمیکنه و حتی اگر پارامتر رو به صورت by ref ارسال نکنیم باز هم کپی محلی از پارامتر گرفته نمیشه و انگار داریم by ref ارجاعش میدیم .


چون داخل Thread1 هستیم پس ترد منتظر اتمام روال Collect در ترد اصلی نمی مونه و بلافاصله aStrList.Free رو شروع می کنه؟
بله .


اگر به این صورت هم باشه اونموقع آزاد کردن aStrList در داخل تابع Collect چه صورتی داره؟ به هر صورت این متغیر یک متغیر محلی از روال Execute هست و تا اون موقع ممکنه اجرای Execute در Thread1 به پایان رسیده باشه.
یعنی بعد از اتمام عملیات Collect بیایم و aStrList رو آزاد کنیم ؟ تو تابع Collect به aStrList دسترسی ندارم که ، Collect متدی از کلاس TForm1 هست .


از طرفی جای دیگه ای هم به این متغیر دسترسی نداریم، مگر اینکه aStrList بصورت یک Property یا Field در خود کلاس TThread1 تعریف بشه و در Destructor همون کلاس آزادش کنیم؟
بله باید همین کار رو بکنید ، من وقتش رو نداشتم تا کد رو کامل کنم و فقط به عنوان نکته گفتم که یادتون نره آزادش بکنید .

راه دیگری هم هست اون هم اینکه بعد از اینکه کار ترد تموم شد در رویداد Collect یا ... با استفاده از تابع PostThreadMessage یک پیغام به ترد ارسال کنید ( مثلا WM_Finish ) و تو ترد بررسی کنید اگر این پیغام رسید aStrList رو ازاد کنید ؛ البته برای این کار باید برای Thread تون یک Message Handler بنویسید ولی در کل برای این مورد کار فنی نیست و بهتره عملیات آزاد سازی حافظه رو تو همون destructor ترد انجام بدید .

SAASTN
چهارشنبه 31 فروردین 1390, 22:46 عصر
یعنی بعد از اتمام عملیات Collect بیایم و aStrList رو آزاد کنیم ؟ تو تابع Collect به aStrList دسترسی ندارم که ، Collect متدی از کلاس TForm1 هست .
چرا دیگه، aStrList رو بصورت پارامتر برای Collect ارسال کردیم.

اما باز که فکر می کنم می بینم این آزادسازی در Destructor ترد هم نمی تونه انجام بشه، چون ممکنه ترد به انتهای کارش برسه و اتوماتیک تخریب بشه و اینجا هم تضمینی وجود نداره که روال Collect به پایان رسیده باشه. از طرفی اگه aStrList توی Destructor آزاد نشه دسترسی که از طریق پارامتر توی Collect بهش داریم هنوز معتبره و می تونیم اونجا آزادش کنیم.

Felony
چهارشنبه 31 فروردین 1390, 23:01 عصر
چرا دیگه، aStrList رو بصورت پارامتر برای Collect ارسال کردیم.
این هم میشه ولی راهی که پائین ارائه دادم بهتره .


اما باز که فکر می کنم می بینم این آزادسازی در Destructor ترد هم نمی تونه انجام بشه، چون ممکنه ترد به انتهای کارش برسه و اتوماتیک تخریب بشه و اینجا هم تضمینی وجود نداره که روال Collect به پایان رسیده باشه. از طرفی اگه aStrList توی Destructor آزاد نشه دسترسی که از طریق پارامتر توی Collect بهش داریم هنوز معتبره و می تونیم اونجا آزادش کنیم.
تردها رو به صورت عمومی تعریف کنید و FreeOnTerminate تردهاتون رو Flase کنید و بعد از اتمام کار تابع Collect تردها رو آزاد کنید ، با این کار هم ترد آزاد میشه هم aStrList و هم مطمئن هستید کار تابع Collect تموم شده .

البته نظر من این هست که یک کلاس نوشته بشه شامل تردهای مورد نیاز برای انجام عملیات ها به همراه یک ترد Collector ؛ آزاد کردن اشیا ( aStrList ) رو به Destructor تردها بسپرید و آزاد سازی تردها رو به ترد Collector اینجوری همه چیز خیلی راحت رو به راه میشه .

vcldeveloper
پنج شنبه 01 اردیبهشت 1390, 00:40 صبح
پس راهش استفاده از SendMessage هست ؟نه، با فراخوانی SendMessage، تا زمانی که Thread اصلی پیام شما رو پردازش نکنه، Thread ارسال کننده پیام بلوکه میشه!


اما وقتی کار دو Thread هم زمان تموم بشه ، برای دسترسی به TForm1.Collect به مشکل بر نمیخورن ؟ میخورند، اون کد اصلا Thread-safe نیست.


آقا سوال اینکه مگه اگر StringList بصورت Call By Value ارسال بشه چه میزان دیتا ردوبدل میشه. من تصورم این بود که خود متغیر مربوط به اشیا در دلفی تنها یه اشاره گر به محل allocate شده برای شیئ هستند. که اگه اینطور باشه در عمل تفاوتی در ارسال یک شئ بصورت فراخوانی با مقدار یا فراخوانی با آدرس از نظر حافظه ای وجود نداره. در اولی مقدار یک اشاره گر به محل تخصیص ارسال میشه و در دومی یک اشاره گر به آدرس خود متغیر شیئی.اشیاء در دلفی به صورت یک اشاره گر به یک ساختار در heap پیاده سازی شدند؛ وقتی یک شی به عنوان پارامتر به تابعی ارسال میشه، اگر به صورت by value ارسال بشه، مقدار همون اشاره گر به تابع ارسال میشه. اگر به صورت by reference ارسال بشند، به صورت یک یک اشاره گر به اشاره گر شی مربوطه پیاده سازی میشند، که عملا چندان با معنی نیست، البته اون کار در پشت صحنه صورت میگیره، و شما نیازی ندارید که دو بار اون اشاره گر رو dereference کنید. نتیجه گیری کلی اش این هست که اشیاء چه به صورت by value و چه به صورت by reference، همیشه به عنوان یک اشاره گر به تابع ارسال میشند، و محتوای اصلی شی که در heap ذخیره شده، کپی نمیشه. درباره اش قبلا یک مقاله مفصل اینجا نوشتم:
http://vcldeveloper.com/articles/different-function-parameter-modifiers-in-delphi/


اگه روش بهتری برای ارسال هم زمان نتایج دو یا چند Thread ، به Main Thread وجود داشته داشته باشه . یک ساختار داده برای نگهداری اون حجم از اطلاعات که توسط Thread ها ارسال میشه، تعریف کنید؛ مثلا اگر Thread های شما یک سری رکورد ارسال می کنند که باید به Thread اصلی برسه، می تونید یک ساختار صف برای نگهداری اون داده ها ایجاد کنید؛ این صف؛ داده اشتراکی بین Thread های شما ست، پس باید ورودی و خروجی داده از اون صف رو Synchronize کنید. هر Thread که در حین پردازشش نیاز به ارسال داده پیدا میکنه، باید داده رو در این صف قرار بده، و سپس یک پیام برای Thread اصلی ارسال کنه که داده جدیدی در صف قرار گرفته. Thread اصلی هم با دریافت پیام، صف رو چک میکنه، و پیام رو واکشی میکنه.

برای ارسال پیام، می تونید هم از تابع PostMessage استفاده کنید (نه SendMessage)، هم از متد Queue مربوط به کلاس TThread که یک متد رو در Context مربوط به Thread مقصد، به صورت Asynchronous اجرا میکنه.

برای ساخت صف مورد نظر، می تونید از کلاس جنریک TQueue<T> در Generics.Collection استفاده کنید، و با استفاده از System.TMonitor دسترسی بهش رو بین Thread های مختلف Synchronize کنید.

مهران رسا
پنج شنبه 01 اردیبهشت 1390, 13:11 عصر
این صف؛ داده اشتراکی بین Thread های شما ست، پس باید ورودی و خروجی داده از اون صف رو Synchronize کنید

آقای کشاورز با این اوصاف نمیشه کدی که آقای تاجیک نوشتند رو به شکل زیر تغییر داد ؟

var
aStrList: TStringList;
procedure TThread1.Execute;
var
i: Integer;
begin
aStrList:= TStringList.Create;
for i:= 1 to 1000 do
aStrList.Add(IntToStr(i));
Synchronize(SendToMainThread);
end;

procedure TThread1.SendToMainThread;
begin
Form1.Collect(aStrList);
end;


با استفاده از System.TMonitor دسترسی بهش رو بین Thread های مختلف Synchronize کنید. فرقش با متد Synchronize چیه ؟

vcldeveloper
پنج شنبه 01 اردیبهشت 1390, 18:22 عصر
آقای کشاورز با این اوصاف نمیشه کدی که آقای تاجیک نوشتند رو به شکل زیر تغییر داد ؟
نه.


فرقش با متد Synchronize چیه ؟
مفهوم Synchronization رو با متد Synchronize اشتباه نگیرید. Synchronization (یا همزمان سازی) فرایندی برای همزمان سازی چند کار در حال اجرا یا سریال سازی اون کارها ست؛ مثلا اگر چهار نفر با هم قرار میذارند که در نقطه A همدیگر رو ببیند، و سپس به طرف نقطه B حرکت کنند، افراد ممکنه در زمان های مختلفی به نقطه A برسند، ولی هر کی به اون نقطه رسید، منتظر سایرین هست، وقتی همه رسیدند، راه میافته. یا اگر اون 4 نفر یک لیوان آب داشته باشند، هر چهار نفر نمی تونند با هم آب بخورند، بلکه باید صف بندی بشه، تا یکی یکی بتونند آب بخورند. اگر صفی وجود نداشته باشه، همه با هم می ریزند سر لیوان و دعوا می کنند. پس تکنیک های مختلفی که شما برای پرهیز از اینگونه شرایط بین Thread ها به کار میگیرید، تکنیک های Synchronization هستند. برای پیاده سازی این تکنیک ها هم سیستم عامل یک سری ابزارهای ابتدایی به شما میده که بهشون میگن Primitive Synchronization Objects. ابزارهای برنامه نویسی مختلف هم ممکنه بر اساس این ابزارهای ابتدایی، ابزارهای سطح بالاتری بسازند و در اختیار شما قرار بدند، تا شما کمتر با جزئیات کار درگیر بشید. یکی از این ابزارها Monitor ها هستند. متد TThread.Synchronize هم یکی از این ابزارها هست که البته در اون کارایی فدای سهولت استفاده شده.

برای آشنایی بیشتر با مانیتورها، می تونید به لینک ویکی پدیا زیر مراجعه کنید:

http://en.wikipedia.org/wiki/Monitor_%28synchronization%29

برای آشنایی با نحوه عملکرد System.TMonitor هم می تونید به راهنمای دلفی برای این کلاس رجوع کنید. اگر با دات نت آشنا هستید، کارکرد System.TMonitor مشابه متد Lock در کلاس های دات نت هست.

مهران رسا
پنج شنبه 01 اردیبهشت 1390, 18:39 عصر
نه.
چرا ؟
من از روش آقای تاجیک و متد Synchronize استفاده کردم . اینبار مشکلی پیش نیومد . به صورت هم زمان 30 ترد رو به اجرا در آوردم و نتایج رو بررسی کردم . همه چیز درست بود و در واقع هیچ تداخلی بین Thread ها به وجود نیومد.

vcldeveloper
پنج شنبه 01 اردیبهشت 1390, 18:46 عصر
چرا ؟
من از روش آقای تاجیک و متد Synchronize استفاده کردم . اینبار مشکلی پیش نیومد . به صورت هم زمان 30 ترد رو به اجرا در آوردم و نتایج رو بررسی کردم . همه چیز درست بود و در واقع هیچ تداخلی بین Thread ها به وجود نیومد. وقتی Synchronize رو اضافه می کنید، مشکل همزمان سازی شما برطرف میشه، چون همه درخواست ها برای اضافه کردن گزینه به لیست سریال میشند، اما هر کدوم از Thread ها باید تا پایان کار اون متد Collect منتظر وایستند، که این موجب کاهش کارایی اونها میشه. اگر کارایی فعلی براتون کفایت میکنه، و زمان انتظار مورد نظر براتون قابل توجه نیست، و فقط هم Thread اصلی استفاده کننده این داده ها هست، همون کد هم کار شما رو راه میاندازه.