Babak_King
08-01-2006, 06:22
امين ايزدپناه
در اين مقاله، من در مورد استفاده از delegate ها براي اجراي يك متد به صورت ناهمگام بحث ميكنم. در اينجا من فرض را بر اين ميگيرم كه شما اساس برنامهنويسي با delegateها را ميدانيد.
سناريوهاي متعددي وجود دارند كه در آنها اجراي ناهمگام ميتواند يك تكنيك طراحي بسيار ارزشمند باشد. براي مثال، شما هنگامي كه خواهان اجراي يك دستور طويلالاجرا بدون بلوك شدن پاسخگويي رابط كاربر در يك برنامه Windows® Forms هستيد بايد از اجراي ناهمگام استفاده كنيد. چنان كه خواهيد ديد، برنامهنويسي با delegate ها اجراي يك دستور بر روي يك thread ثانويه به صورت ناهمگام را نسبتا ساده ميسازد. اين بدان معني است كه شما ميتوانيد يك برنامه كاربردي Windows Forms بسازيد كه فراخوانيهاي طويلالاجرا را بر روي شبكه بدون متوقف شدن رابط كاربر اجرا نمايد.
اجراي متدها به صورت ناهمگام همچنين ميتواند به عنوان يك تكنيك مفيد براي يك برنامه كاربردي سمت سرور مورد استفاده قرار گيرد. فرض كنيد كه شما مشغول نوشتن كد يك صفحه ASP.NET براي سرويسدهي به يك درخواست مشتري هستيد. اگر كد شما قرار باشد دو يا چندين فراخواني وقتگير را بر روي يك شبكه اجرا نمايد شما بايد چه كار كنيد؟ براي مثال، فرض كنيد شما بايد يك صفحه ASP.NET بنويسيد كه بايد يك پرس و جو را با يك پايگاه داده راه دور اجرا كند و همچنين بايد يك متد SOAP را بر روي يك Web Service اجرا نمايد تا كار خود را تكميل كند. اگر شما از اجراي متد ناهمگام استفاده نكنيد، شما ميتوانيد در هر لحظه فقط يك فراخواني را بر روي شبكه انجام دهيد. اين بدان معني است كه شما نميتوانيد دومين فراخواني شبكه را تا زماني كه اولين فراخواني شبكه باز گردد آغاز كنيد. اجراي متد ناهمگام با استفاده از delegate ها امكان اجراي دو يا چندين فراخواني شبكه را به صورت همزمان به شما ميدهد. اين ميتواند به ميزان چشمگيري زمان پردازش يك درخواست ASP.NET سمت سرور را كاهش دهد و پاسخگويي برنامه شما را از ديد كاربر بهبود بخشد.
فراخواني يك متد به صورت ناهمگام
حالا كه شما اساس برنامهنويسي delegate ها را ميدانيد، من قصد دارم مثالي را با استفاده از يك شيء delegate براي اجراي يك متد به صورت همگام مطرح كنم. فرض كنيد كه شما يك متد اشتراكي بنام GetCustomerList نوشتهايد كه يك فراخواني وقتگير را بر روي شبكه اجرا ميكند تا اين كار را تكميل نمايد:
Class DataAccessCode
Shared Function GetCustomerList(State As String) As String()
'*** call across network to DBMS or Web Services to retrieve data
'*** pass data back to caller using string array return value
End Function
End Class
در اين مثال، متد GetCustomerList با يك پارامتر رشتهاي واحد كه نام يك ايالت آمريكا را ميپذيرد تعريف شده است. مقدار بازگشتي متد بر اساس يك آرايه رشتهاي كه براي باز گرداندن يك ليست از مشتريها به فراخواننده متد مورد استفاده قرار ميگيرد تعريف شده است.
براي اجراي متد GetCustomerList با استفاده از يك شيء delegate، شما بايد ابتدا يك نوع delegate را با يك امضاي تطبيقي تعريف نماييد. براي مثال، شما ميتوانيد يك نوع delegate بنام GetCustomerListHandler تعريف كنيد كه يك پارامتر رشتهاي بگيرد و يك آرايه رشتهاي برگرداند، مانند اين:
'
*** a delegate for executing handler methods
Delegate Function GetCustomerListHandler(State As String) As String()
حالا كه شما يك نوع delegate با امضاي فراخواني صحيح تعريف كرديد، شما ميتوانيد يك شيء delegate ايجاد كنيد و آن را به متد GetCustomerList مقيد سازيد. هنگامي كه شما يك شيء delegate را ايجاد و مقيد نموده باشيد، ميتوانيد GetCustomerList را با فراخواني متد Invoke شيء delegate به صورت همگام اجرا كنيد:
'
*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute method synchronously
Dim retval As String()
retval = handler1.Invoke("CA")
هنگامي كه شما Invoke را بر روي شيء delegate فراخواني ميكنيد، آن، فراخواني را با اجراي متد اداره كننده براي شما ميفرستد. توجه به اين نكته اهميت دارد كه يك فراخواني به Invoke همگام است. به عبارت ديگر، آن يك فراخواني بلوك كننده است زيرا آن تا زماني كه شيء delegate اجراي GetCustomerList را به پايان برساند باز نميگردد. هنگامي كه GetCustomerList اجراي خود را تكميل ميكند و باز ميگردد، شيء delegate مقدار بازگشتي خود را دريافت ميكند و آن را به فراخواننده متد Invoke باز ميگرداند.
هنگامي كه كامپايلر Visual Basic® .NET يك تعريف نوع delegate را توليد مينمايد، آن متد Invoke را براي پشتيباني اجراي همگام ميافزايد. كامپايلر همچنين دو متد ديگر را – BeginInvoke و EndInvoke – براي پشتيباني اجراي ناهمگام ميافزايد.
شما ميتوانيد مثال قبلي را با استفاده از يك شيء delegate براي اجراي متد GetCustomerList به صورت ناهمگام بازنويسي نماييد. به جاي فراخواني Invoke بر روي شيء delegate، BeginInvoke را فراخواني كنيد، كه مستلزم آن است كه شما هر پارامتر ورودي كه در امضاي فراخواني delegate تعريف شده است را انتقال دهيد. در مثال من، يك پارامتر رشتهاي واحد براي انتقال يك نام وضعيت وجود دارد.
'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute method asynchronously
handler1.BeginInvoke("CA", Nothing, Nothing)
BeginInvoke همچنين دو پارامتر ديگر دريافت ميكند كه بخشي از امضاي فراخواني Invoke نيستند. من اين دو پارامتر را بعدا توضيح خواهد داد.
هنگامي كه شما يك فراخواني به BeginInvoke بر روي يك شيء delegate انجام ميدهيد، شما در واقع از CLR (common language runtime)i درخواست ميكنيد كه اجراي متد اداره كننده را به صورت ناهمگام بر روي يك thread ثانويه آغاز كند. در عين حال كه اجراي يك متد به صورت ناهمگام با يك فراخواني به BeginInvoke قدرتمند است، آن آسان نيز هست زيرا شما مجبور نيستيد دغدغهاي در مورد ايجاد و اداره يك thread ثانويه داشته باشيد. CLR اين را به صورت خودكار براي شما انجام ميدهد.
تصوير 1 نحوه مواجهه CLR با يك فراخواني به BeginInvoke را نشان ميدهد. هنگامي كه شما متد BeginInvoke را فراخواني ميكنيد، شيء delegate يك تقاضا را در يك صف داخلي خاص قرار ميدهد. CLR مجموعهاي حاوي threadهاي كارگر (worker) را نگهداري ميكند كه مسئول سرويسدهي به تقاضاي موجود در اين صف هستند. اجراي ناهمگام محقق ميشود زيرا thread فراخواننده BeginInvoke با thread اجرا كننده متد اداره كننده يكسان نيست.
تصوير 1 – اجراي متد ناهمگام
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
CLR به صورت پويا با بررسي چند فاكتور تعيين مينمايد كه چه تعداد thread كارگر به مجموعه افزوده شوند. در يك برنامه معمولي دسكتاپ، CLR مجموعه را به تعدادي thread محدود ميكند. در يك برنامه پركار سمت سرور كه در آن صفحات كد ASP.NET تعداد زيادي فراخواني همزمان به BeginInvoke انجام ميدهند، CLR اندازه مجموعه را حداكثر تا 25 thread به ازاي هر پردازنده موجود بر روي ماشين ميزبان افزايش ميدهد. اين بدان معني است كه تعداد threadهاي كارگر موجود در مجموعه به نسبت تعداد پردازندهها افزايش پيدا ميكند. براي مثال، حداكثر اندازه مجموعه بر روي يك كامپيوتر سرور ميزبان با چهار پردازنده برابر با 100 thread است.
حالا تفاوتهاي ميان يك فراخواني به Invoke و يك فراخواني به BeginInvoke را بررسي ميكنيم. فرض كنيد شما ميخواهيد لباسهاي كثيف خود را كه در طي يك ماه گذشته جمع كردهايد تميز نماييد. شما ميتوانيد خودتان اين كار را با مراجعه به خشكشويي انجام دهيد يا انجام اين كار را به شخص ديگري محول كنيد.
اگر شما خودتان شخصا به خشكشويي برويد و لباس را در ماشين لباسشويي قرار دهيد كارتان شبيه فراخواني Invoke خواهد بود؛ اين يك كار همگام است. شما بايد در خشكشويي بمانيد تا كار تكميل شود و هنگامي كه آنجا را ترك ميكنيد لباسهاي تميز شده خود را نيز به همراه ميبريد.
تحويل لباسهاي كثيفتان به مسئول خشكشويي همانند يك فراخواني به BeginInvoke است؛ اين يك كار ناهمگام است. پس از آن كه لباسهاي كثيفتان را به مسئول خشكشويي تحويل داديد، شما ميتوانيد فورا به سراغ كارهاي ديگرتان برويد. اما، شما آنجا را به همراه لباسهاي تميز شدهتان ترك نميكنيد. شما آن محل را با يك قبض خشكشويي ترك ميكنيد و بايد در زمان ديگري براي دريافت لباسهاي تميزتان به آنجا باز گرديد. به علاوه، شما بايد مراقب قبض خود باشيد زيرا اگر آن را گم كنيد احتمالا مسئول خشكشويي از پس دادن لباسهاي شما امتناع خواهد نمود.
هنگامي كه شما BeginInvoke را فراخواني ميكنيد، شما در واقع يك درخواست براي اجراي يك متد در خشكشويي CLR تحويل ميدهيد. فراخواني به BeginInvoke بلافاصله باز ميگردد و thread فراخواننده سپس ميتواند به سراغ كارهاي ديگر خود برود بدون اين كه مجبور باشد در انتظار اجراي متد مورد نظر از سوي CLR باشد. پس از فراخواني BeginInvoke شما ميدانيد كه CLR قصد اجراي آن متد مورد نظر را بر روي thread ثانويه دارد، اما شما نميدانيد كه دقيقا چه هنگام اين اتفاق رخ خواهد داد. بعدا در زمان ديگري، شما ميتوانيد از CLR بپرسيد كه آيا اجراي آن متد را به صورت ناهمگام به پايان رسانده است. شما همچنين ميتوانيد مقدار بازگشتي متد را بازيابي كنيد. اما، مانيتورينگ وضعيت يك فراخواني ناهمگام يا به دست آوردن مقدار بازگشتي يك متد ناهمگام مستلزم آن است كه اطلاعاتي را در مورد درخواست ناهمگامي كه شما مشغول بررسي آن هستيد براي CLR فراهم كنيد.
IAsyncResult
يك فراخواني به BeginInvoke چيزي را برميگرداند كه معادل با يك قبض خشكشويي است. اگر بخواهيم دقيقتر بگوييم، متد BeginInvoke براي برگرداندن شيئي كه يك رابط بنام IAsyncResult را پيادهسازي ميكند طراحي شده است. اينجا مثالي از فراخواني BeginInvoke و دريافت شيء IAsyncResult (قبض خشكشويي) در هنگام ارسال يك فراخواني متد ناهمگام مشاهده ميكنيد:
'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute method asynchronously and capture IAsyncResult object
Dim ar As System.IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)
شيء IAsyncResult به شما امكان مانيتور كردن يك فراخواني ناهمگام در حال پيشروي را ميدهد. آن همچنين امكان بازيابي مقدار بازگشتي و هر پارامتر خروجي را در هنگام پايان يافتن اجراي يك متد ناهمگام براي شما فراهم ميآورد.
درك اين نكته از سوي شما اهميت دارد كه لزوما يك رابطه يك-به-يك ميان يك شيء delegate و يك شيء IAsyncResult وجود ندارد. براي مثال، يك شيء delegate ميتواند براي آغاز دو يا چندين فراخواني ناهمگام كه به صورت همزمان اجرا خواهند شد مورد استفاده قرار گيرد، به اين صورت:
'
*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute multiple methods asynchronously
Dim ar1, ar2 As System.IAsyncResult
ar1 = handler1.BeginInvoke("CA", Nothing, Nothing)
ar2 = handler1.BeginInvoke("WA", Nothing, Nothing)
تصوير 2
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
در اين مثال، دو فراخواني ناهمگام مختلف با هم آغاز ميشوند. هر كدام از اين فراخوانيهاي ناهمگام بر روي يك thread مجزا از مجموعه thread كارگر اجرا خواهند شد، چنان كه در تصوير 2 نشان داده شده است. اين ميتواند ارزشمند باشد چون شما ميتوانيد دو يا چندين فراخواني را به صورت موازي بر روي شبكه انجام دهيد. اما، يادآوري اين نكته براي شما حائز اهميت است كه هر يك از اين فراخوانيهاي ناهمگام داراي شيء IAsyncResult مرتبط با خود است. رابط IAsyncResult يك خصيصه بنام IsComplete را ارائه ميكند كه امكان مانيتور كردن وضعيت يك فراخواني ناهمگام و تعيين اين كه آيا آن تكميل شده است را براي شما فراهم ميآورد. در عين حال كه اين نوع تكنيك polling در اغلب طراحيها كارآمد نيست، آن ميتواند در برخي شرايط مفيد واقع شود. اين يك مثال از استفاده از خصيصه IsComplete براي تعيين اين كه آيا CLR اجراي يك فراخواني ناهمگام را به اتمام رسانده است، ميباشد:
Dim ar As IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)
'*** allow some time to pass
'*** check to see if the async call has completed
If (ar.IsCompleted) Then
'*** retrieve method return value
End If
فراخواني EndInvoke
شما با فراخواني متد EndInvoke بر روي delegate، مقادير بازگشتي و هر پارامتر خروجي را بازيابي ميكنيد. EndInvoke با همان نوع بازگشت مقدار بازگشتي امضاي فراخواني نوع delegate تعريف ميشود:
Dim ar As IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)
'*** allow some time to pass
Dim retval As String()
retval = handler1.EndInvoke(ar)
چنان كه مشاهده ميكنيد، فراخواني EndInvoke امكان بازيابي مقدار بازگشتي از يك فراخواني متد ناهمگام را به شما ميدهد. همچنين توجه داشته باشيد كه يك فراخواني به EndInvoke شما را ملزم به انتقال شيء IAsyncResult مرتبط با يك فراخواني ناهمگام خاص مينمايد. اين درست شبيه دادن قبضتان به مسئول خشكشويي هنگام بازگشتتان براي دريافت لباسهاي تميز است. شيء IAsyncResult اين امكان را براي CLR فراهم ميآورد كه تعيين نمايد كدام فراخواني ناهمگام مورد نظر شماست.
همچنين توجه داشته باشيد كه ليست پارامتر براي EndInvoke شامل هر پارامتر ByRef تعريف شده درون نوع delegate خواهد بود از اين رو شما همچنين ميتوانيد هر پارامتر خروجي را بازيابي نماييد. اما، مثالي كه من در اينجا مطرح كردهام دربردارندهي هيچ پارامتر خروجي نيست، بنابراين فراخواني به EndInvoke فقط مستلزم آن است كه شما يك پارامتر واحد نگهدارنده يك ارجاع به شيء IAsyncResult را انتقال دهيد.
شما ديديد كه يك فراخواني به EndInvoke اهميت دارد زيرا آن امكان بازيابي مقدار بازگشتي و هر پارامتر خروجي را به شما ميدهد. فراخواني EndInvoke همچنين حياتي است زيرا آن به شما اين امكان را ميدهد كه ببينيد آيا متد ناهمگام با موفقيت اجرا شده است. اگر متد ناهمگام با يك استثناء اداره نشده مواجه شود، آن شيء استثناء توسط CLR تحت نظر قرار ميگيرد و سپس هنگامي كه شما EndInvoke را فراخواني ميكنيد ايجاد ميگردد (throw ميشود)، چنان كه در كد زير نشان داده شده است:
Dim retval As String()
Try
retval = handler1.EndInvoke(ar)
Catch ex As Exception
'*** deal with exception here
End Try
مكمل كردن هر فراخواني به BeginInvoke با يك فراخواني به EndInvoke مهم است. اگر شما EndInvoke را فراخواني نكنيد، شما هرگز نميتوانيد واقعا اطمينان يابيد كه يك فراخواني ناهمگام با موفقيت اجرا شده است. فراموش كردن انجام فراخواني EndInvoke ميتواند باعث ناديده گرفته شدن استثناءهاي زمان اجرا شود، كه ميتواند باگهايي را در كد شما به وجود آورد كه يافتن و اصلاح آنها دشوار است.
شما تا اينجا چند دليل خوب براي اين سوال يافتيد كه چرا يك فراخواني به BeginInvoke نيازمند يك فراخواني به EndInvoke است. در اينجا يك دليل بسيار مهم ديگر براي اين سوال وجود دارد كه چرا شما بايد همواره EndInvoke را فراخواني كنيد – اين قانون است. فراموش كردن انجام فراخواني EndInvoke مانع از اين خواهد شد كه CLR برخي از منابع مورد نياز در انتقال فراخوانيهاي ناهمگام را به دست آورد. بنابراين، شما بايد اين گونه تصور كنيد كه اگر فراخوانيهايي را به BeginInvoke انجام دهيد بدون اين كه فراخوانيهاي مرتبطي را نيز به EndInvoke انجام دهيد برنامه شما نشت (leak) خواهد داشت.
حالا بياييد توجه خود را به نحوه عمل زمانبندي در زماني كه شما اقدام به فراخواني EndInvoke ميكنيد مبذول نماييم. يك فراخواني به EndInvoke در صورتي كه thread كارگر اجراي متد اداره كننده را تكميل كرده باشد بلافاصله باز ميگردد. اما، يك فراخواني به EndInvoke در صورتي كه فراخواني ناهمگام هنوز آغاز نشده باشد يا هنوز در حال انجام باشد بلوك خواهد شد. شما در هنگام طراحي برنامههاي خود بايد به پيآمدهاي اين واقعيت توجه داشته باشيد.
وضعيتي را در نظر بگيريد كه در آن شما كد مورد نياز براي يك صفحه ASP.NET را نوشتهايد و خواهان اجراي دو فراخواني به صورت همزمان بر روي شبكه هستيد. اين واقعيت كه EndInvoke يك فراخواني بلوك كننده است هماهنگ كردن چندين مسير اجرا را بسيار تسهيل مينمايد. كد موجود در تصوير 3 دو متد را به صورت همزمان و ناهمگام اجرا ميكند تا دو فراخواني شبكه را به صورت موازي انجام دهد. اما، اين مهم است كه هر دو فراخواني شبكه قبل از آن كه تقاضا بتواند به صورت كامل به پايان برسد با موفقيت تكميل ميشوند. اولين فراخواني به EndInvoke بلوك ميشود تا اولين متد ناهمگام تكميل گردد. فراخواني دوم به متد EndInvoke بلوك ميشود تا دومين فراخواني تكميل گردد.
اگر دومين فراخواني ناهمگام قبل از اولين فراخواني ناهمگام به اتمام برسد چه اتفاقي ميافتد؟ اين به هيچ وجه اهميت ندارد. در اين طراحي اهميتي ندارد كه كدام يك از دو فراخواني شبكه ابتدا به پايان ميرسد. هر دو فراخواني به GetCustomerList بايد قبل از آن كه تقاضاي سمت سرور بتواند كار خود را به اتمام برساند تكميل شوند. بنابراين، رفتار بلوك كننده EndInvoke نقش مهمي را ايفا مينمايد. آن چند مسير اجرا را دريافت ميكند و آنها را براي ايجاد يك مسير منطقي اجرا به يكديگر ملحق مينمايد.
در تمام مدت، CLR همهي كارهاي پيچيده پشت صحنه را انجام ميدهد. آن threadها را مديريت ميكند، فراخوانيهاي ناهمگام را انتقال ميدهد، و threadهاي معيني را بلوك مينمايد تا ساير threadها كارشان را تكميل نمايند. تمام كاري كه شما بايد انجام دهيد فراخواني BeginInvoke و سپس فراخواني EndInvoke است. شما ميتوانيد آشكارا ببينيد كه delegateها يك ايده قدرتمند آسان را براي اجراي متد ناهمگام فراهم ميآورند.
حالا بياييد توجه خود را به اجراي متد ناهمگام در يك برنامه Windows Forms در حال اجرا بر روي يك برنامه دسكتاپ مبذول نماييم. اين يك سناريوي كاملا متفاوت از يك برنامه سمت سرور است زيرا اينجا تمامي كد معمولا بر روي يك thread واحد كه تحت عنوان UI thread اوليه شناخته ميشود اجرا ميگردد. UI thread اوليه در يك برنامه Windows Forms UI اهميت زيادي دارد زيرا آن مسئول حفظ و اداره پاسخگويي رابط كاربر است. اگر شما رابط كاربر اوليه را با يك فراخواني طويلالاجرا بر روي شبكه متوقف سازيد، برنامه ميزبان تا بازگشت فراخواني پاسخگوي كاربر نخواهد بود.
در اين حالت استفاده از اجراي ناهمگام حياتي است تا شما موجب توقف رابط كاربر نشويد. شما ديديد كه ارسال يك فراخواني ناهمگام با استفاده از متد BeginInvoke بسيار ساده است، اما يك مشكل وجود دارد. هنگامي كه يك فراخواني متد ناهمگام به اجرا پايان داد برنامه شما چگونه واكنش نشان ميدهد؟ هنگامي كه يك فراخواني ناهمگام خاتمه يافت شما چگونه رابط كاربر را به روز رساني ميكنيد؟ شما ميتوانيد از يك تكنيك polling استفاده كنيد و پيوسته تست تكميل انجام دهيد، اما اين زياد كارآمد نيست. در عوض، شما بايد از يك ويژگي مفيد delegateها بهره ببريد كه امكان برپا كردن يك متد بازخواني كه هنگام تكميل شدن يك فراخواني متد ناهمگام به صورت خودكار توسط CLR اجرا ميشود را براي شما فراهم ميكند.
كد موجود در تصوير 4 مثالي از يك برنامه Windows Forms را نشان ميدهد كه متد GetCustomerList را به صورت ناهمگام اجرا ميكند. تمامي كد درون يك كلاس بنام Form1 نوشته شده است. درون اين تعريف كلاس، شما ميتوانيد يك رويكرد ممكن براي برپايي يك متد بازخواني در هنگام انتقال يك فراخواني متدناهمگام با استفاده از BeginInvoke را مشاهده كنيد.
بياييد اين كد را بررسي كنيم. هنگامي كه شما خواهان برپايي يك متد بازخواني هستيد، شما بايد با يك نوع delegate بنام AsyncCallback كار كنيد كه توسط Framework Class Library در فضانام System تعريف شده است. هنگامي كه شما خواهان ايجاد يك متد بازخواني هستيد، بايد اين متد را با يك امضاي فراخواني تعريف كنيد كه با امضاي فراخواني نوع AsyncCallback delegate مطابقت داشته باشد. MyCallbackMethod در تصوير 4 يك مثال از يك متد تعريف شده با امضاي فراخواني مناسب است. چنان كه مشاهده ميكنيد، متد بازخواني بايد به عنوان يك رويهي Sub با يك پارامتر واحد از نوع IAsyncResult تعريف شود.
شما ميبينيد كه كلاس Form1 حاوي دو فيلد است، كه هر كدام يك ارجاع به يك شيء delegate را نگه ميدارند. فيلد اول، TargetHandler، يك ارجاع به يك شيء delegate نگه ميدارد كه براي انتقال فراخواني ناهمگام به متد GetCustomerList به كار برده ميشود. فيلد دوم، CallbackHandler، يك ارجاع به يك شيء delegate نگه ميدارد كه به MyCallbackMethod مقيد است. اين اشياء delegate هر دو در فراخواني به BeginInvoke به كار برده ميشوند.
حالا فراخواني به BeginInvoke درون متد اداره كننده رويداد cmdExecuteTask را مورد بررسي قرار دهيم. فيلد TargetHandler براي اجراي BeginInvoke و انتقال فراخواني ناهمگام مورد استفاده قرار ميگيرد. پارامتر دوم انتقال يافته به BeginInvoke، ارجاع به شيء delegateي است كه به MyCallbackMethod مقيد ميباشد. در واقع، شما يك ارجاع را به يك delegate انتقال ميدهيد تا به CLR بگوييد كه خواهان استفاده از كدام متد بازخواني هستيد. اين همه آن چيزي است كه براي برپايي يك متد بازخواني پس از يك فراخواني متد ناهمگام مورد نياز است.
حالا بياييد ببينيم هنگامي كه يك فراخواني ناهمگام انتقال مييابد اجزاء مثال من چگونه عمل مينمايند. هنگامي كه برنامه BeginInvoke را فراخواني ميكند، CLR متد اداره كننده را با استفاده از يك thread كارگر از مجموعه thread متعلق به CLR اجرا ميكند. سپس، همان thread كارگر متد بازخواني را اجرا مينمايد. هنگامي كه اين كار كامل شد، CLR آن thread كارگر را به مجموعه باز ميگرداند تا آن براي سرويسدهي به تقاضاهاي ناهمگام ديگر در دسترس باشد.
درك اين نكته بسيار حائز اهميت است كه متد بازخواني بر روي UI thread اوليهاي كه فراخواني به BeginInvoke را انجام داده است اجرا نميشود. يك بار ديگر هم ميگوييم كه متد بازخواني بر روي همان thread ثانويهاي كه متد ناهمگام را اجرا كرده است اجرا ميشود.
آخرين پارامتر انتقال يافته در يك فراخواني به BeginInvoke، پارامتر AsyncState است. AsyncState يك پارامتر بسيار بي محدوديت است كه شما ميتوانيد از آن براي انتقال هر چيزي كه ميخواهيد استفاده كنيد. آن بر اساس كلاس Object تعريف شده است از اين رو ميتواند براي انتقال هر مقدار يا شيئي به كار برده شود. در مثالي كه من در تصوير 4 ارائه نمودهام، هيچ نيازي به استفاده از پارامتر AsyncState نبود، به همين دليل يك مقدار Nothing انتقال يافت.
در ساير طراحيها، استفاده از پارامتر AsyncState ميتواند براي انتقال يك مقدار يا يك شيء از كدي كه BeginInvoke را فراخواني ميكند به پيادهسازي متد بازخواني مناسب باشد. براي مثال، فرض كنيد كه شما خواهان انتقال يك Integer هستيد تا در پيادهسازي متد بازخواني در دسترس قرار گيرد. هنگامي كه شما BeginInvoke را فراخواني ميكنيد، شما ميتوانيد مقدار Integer را در پارامتر AsyncState انتقال دهيد، به اين صورت:
Dim MyValue As Integer = 100
TargetHandler.BeginInvoke("CA", CallbackHandler, MyValue)
مقدار يا شيئي كه در شيء AsyncState انتقال يافته توسط شيء IAsyncResult پيگيري ميشود ميتواند با استفاده از يك خصيصه بنام AsyncState بازيابي گردد. اينجا شاهد مثالي از بازيابي مقدار Integer در يك متد بازخواني هستيد:
Sub MyCallbackMethod(ByVal ar As IAsyncResult)
Dim y As Integer = CInt(ar.AsyncState)
'*** other code omitted for brevity
End Sub
به ياد داشته باشيد كه خصيصه AsyncState بر اساس كلاس Object تعريف شده است، از اين رو شما معمولا بايد آن را صريحا به يك نوع خاصتر تبديل كنيد قبل از آن كه بتوانيد با مقدار يا شيئي كه درون آن است برنامه بنويسيد.
شما ديديد كه فراخواني BeginInvoke از كد متعلق به يك Windows Form براي انتقال يك فراخواني متد ناهمگام نسبتا آسان است. شما همچنين آموختيد كه چگونه ميتوان يك متد بازخواني را كه در هنگام تكميل شدن فراخواني متد ناهمگام به صورت خودكار عمل مينمايد برپا كرد. آخرين چيزي كه شما بايد بياموزيد نحوه به روز رساني UI است تا به كاربر بگوييد كه كار فراخواني ناهمگام پايان يافته است. اين ميتواند دشوار باشد. نوشتن كد به شكلي درست دشوار است و اگر شما اين كار را به درستي انجام ندهيد احتمالا با مشكلات جدي روبرو خواهيد شد.
يك قاعده threading مهم وجود دارد كه بايد در هنگام برنامهنويسي با Windows Forms از آن پيروي كرد. UI thread اوليه تنها threadي است كه مجاز به دسترسي به يك شيء فرم و تمامي كنترلهاي فرزند آن است. اين بدان معني است كه كدي كه بر روي يك thread ثانويه اجرا ميشود مجاز به دسترسي به متدها و خصيصههاي يك فرم يا يك كنترل نيست. اما، شما بايد به خاطر داشته باشيد كه يك متد بازخواني از قبيل MyCallbackMethod بر روي يك thread ثانويه اجرا ميشود نه بر روي UI thread اوليه. اين بدان معني است كه شما هرگز نبايد مستقيما از اين نوع متد بازخواني مبادرت به بروزرساني رابط كاربر نماييد.
به عبارت ديگر، متد بازخواني كه بر روي thread ثانويه اجرا ميشود بايد UI thread اوليه را وادار به اجراي كدي نمايد كه رابط كاربر را به روز رساني كند. كد مورد نياز براي انجام اين كار نوشته شده است و من در قسمت بعدي مقاله دقيقا شرح خواهم داد كه چگونه اين كار را انجام دهيد. فعلا فقط اين را بدانيد كه به روز رساني رابط كاربر مستقيا از يك متد بازخواني همچون MyCallbackMethod به دليل محدوديتهاي threading اعمال شده از جانب framework يك تكنيك برنامهنويسي قابل قبول نيست.
نتيجه
در هنگام اجراي يك فراخواني متد ناهمگام با استفاده از يك delegate، هر نوع delegate يك متد BeginInvoke را براي آغاز يك فراخواني متد به صورت ناهمگام بر روي يك thread از يك مجموعه thread نگهداري شده توسط CLR فراهم ميآورد. هر نوع delegate متد EndInvoke مكمل را فراهم مينمايد كه ميتواند براي به دست آوردن مقدار بازگشتي يك متد ناهمگام و براي تعيين اين كه آيا متد بدون هيچ استثناء اداره نشدهاي اجرا شده است مورد استفاده قرار گيرد.
يكي از ارزشمندترين جنبههاي اجراي ناهمگام با استفاده از delegate ها اين است كه لازم نيست شما دغدغهاي در مورد ايجاد و اداره threadهاي ثانويه داشته باشيد. CLR به صورت خودكار يك مجموعه از threadهاي كارگر را براي شما نگهداري ميكند، كه اين بدان معني است كه شما ميتوانيد از تكنيكهايي شامل اجراي متد ناهمگام و موازي بدون نياز به برنامهنويسي مستقيم با threadها بهره ببريد. كل چيزي كه براي آموختن باقي ميماند نحوه فراخواني BeginInvoke و EndInvoke در زمانهاي مناسب است.
تصوير 3 – اجراي چندين متد به صورت ناهمگام.
'
*** server-side request in code behind an ASP.NET page
'*** execute multiple methods asynchronously
Dim ar1, ar2 As System.IAsyncResult
ar1 = handler1.BeginInvoke("CA", Nothing, Nothing)
ar2 = handler1.BeginInvoke("WA", Nothing, Nothing)
'*** capture all return values
Dim retval1, retval2 As String()
retval1 = handler1.EndInvoke(ar1)
retval2 = handler1.EndInvoke(ar2)
'*** finish processing request
تصوير 4 – متد بازخواني.
Public Class Form1 : Inherits System.Windows.Forms.Form
Private TargetHandler As GetCustomerListHandler _ = AddressOf DataAccessCode.GetCustomerList
Private CallbackHandler As AsyncCallback _
= AddressOf MyCallbackMethod
Sub cmdExecuteTask_Click(sender As Object, e As EventArgs) _
Handles cmdExecuteTask.Click
'*** execute method asynchronously with callback
TargetHandler.BeginInvoke("CA", CallbackHandler, Nothing)
End Sub
'*** callback method runs on worker thread and not the UI thread
Sub MyCallbackMethod(ByVal ar As IAsyncResult)
'*** this code fires at completion of each asynchronous method call
Dim retval As String()
retval = TargetHandler.EndInvoke(ar)
End Sub
End Class
منبع:سايت ماهنامه علم الكترونيك و كامپيوتر
در اين مقاله، من در مورد استفاده از delegate ها براي اجراي يك متد به صورت ناهمگام بحث ميكنم. در اينجا من فرض را بر اين ميگيرم كه شما اساس برنامهنويسي با delegateها را ميدانيد.
سناريوهاي متعددي وجود دارند كه در آنها اجراي ناهمگام ميتواند يك تكنيك طراحي بسيار ارزشمند باشد. براي مثال، شما هنگامي كه خواهان اجراي يك دستور طويلالاجرا بدون بلوك شدن پاسخگويي رابط كاربر در يك برنامه Windows® Forms هستيد بايد از اجراي ناهمگام استفاده كنيد. چنان كه خواهيد ديد، برنامهنويسي با delegate ها اجراي يك دستور بر روي يك thread ثانويه به صورت ناهمگام را نسبتا ساده ميسازد. اين بدان معني است كه شما ميتوانيد يك برنامه كاربردي Windows Forms بسازيد كه فراخوانيهاي طويلالاجرا را بر روي شبكه بدون متوقف شدن رابط كاربر اجرا نمايد.
اجراي متدها به صورت ناهمگام همچنين ميتواند به عنوان يك تكنيك مفيد براي يك برنامه كاربردي سمت سرور مورد استفاده قرار گيرد. فرض كنيد كه شما مشغول نوشتن كد يك صفحه ASP.NET براي سرويسدهي به يك درخواست مشتري هستيد. اگر كد شما قرار باشد دو يا چندين فراخواني وقتگير را بر روي يك شبكه اجرا نمايد شما بايد چه كار كنيد؟ براي مثال، فرض كنيد شما بايد يك صفحه ASP.NET بنويسيد كه بايد يك پرس و جو را با يك پايگاه داده راه دور اجرا كند و همچنين بايد يك متد SOAP را بر روي يك Web Service اجرا نمايد تا كار خود را تكميل كند. اگر شما از اجراي متد ناهمگام استفاده نكنيد، شما ميتوانيد در هر لحظه فقط يك فراخواني را بر روي شبكه انجام دهيد. اين بدان معني است كه شما نميتوانيد دومين فراخواني شبكه را تا زماني كه اولين فراخواني شبكه باز گردد آغاز كنيد. اجراي متد ناهمگام با استفاده از delegate ها امكان اجراي دو يا چندين فراخواني شبكه را به صورت همزمان به شما ميدهد. اين ميتواند به ميزان چشمگيري زمان پردازش يك درخواست ASP.NET سمت سرور را كاهش دهد و پاسخگويي برنامه شما را از ديد كاربر بهبود بخشد.
فراخواني يك متد به صورت ناهمگام
حالا كه شما اساس برنامهنويسي delegate ها را ميدانيد، من قصد دارم مثالي را با استفاده از يك شيء delegate براي اجراي يك متد به صورت همگام مطرح كنم. فرض كنيد كه شما يك متد اشتراكي بنام GetCustomerList نوشتهايد كه يك فراخواني وقتگير را بر روي شبكه اجرا ميكند تا اين كار را تكميل نمايد:
Class DataAccessCode
Shared Function GetCustomerList(State As String) As String()
'*** call across network to DBMS or Web Services to retrieve data
'*** pass data back to caller using string array return value
End Function
End Class
در اين مثال، متد GetCustomerList با يك پارامتر رشتهاي واحد كه نام يك ايالت آمريكا را ميپذيرد تعريف شده است. مقدار بازگشتي متد بر اساس يك آرايه رشتهاي كه براي باز گرداندن يك ليست از مشتريها به فراخواننده متد مورد استفاده قرار ميگيرد تعريف شده است.
براي اجراي متد GetCustomerList با استفاده از يك شيء delegate، شما بايد ابتدا يك نوع delegate را با يك امضاي تطبيقي تعريف نماييد. براي مثال، شما ميتوانيد يك نوع delegate بنام GetCustomerListHandler تعريف كنيد كه يك پارامتر رشتهاي بگيرد و يك آرايه رشتهاي برگرداند، مانند اين:
'
*** a delegate for executing handler methods
Delegate Function GetCustomerListHandler(State As String) As String()
حالا كه شما يك نوع delegate با امضاي فراخواني صحيح تعريف كرديد، شما ميتوانيد يك شيء delegate ايجاد كنيد و آن را به متد GetCustomerList مقيد سازيد. هنگامي كه شما يك شيء delegate را ايجاد و مقيد نموده باشيد، ميتوانيد GetCustomerList را با فراخواني متد Invoke شيء delegate به صورت همگام اجرا كنيد:
'
*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute method synchronously
Dim retval As String()
retval = handler1.Invoke("CA")
هنگامي كه شما Invoke را بر روي شيء delegate فراخواني ميكنيد، آن، فراخواني را با اجراي متد اداره كننده براي شما ميفرستد. توجه به اين نكته اهميت دارد كه يك فراخواني به Invoke همگام است. به عبارت ديگر، آن يك فراخواني بلوك كننده است زيرا آن تا زماني كه شيء delegate اجراي GetCustomerList را به پايان برساند باز نميگردد. هنگامي كه GetCustomerList اجراي خود را تكميل ميكند و باز ميگردد، شيء delegate مقدار بازگشتي خود را دريافت ميكند و آن را به فراخواننده متد Invoke باز ميگرداند.
هنگامي كه كامپايلر Visual Basic® .NET يك تعريف نوع delegate را توليد مينمايد، آن متد Invoke را براي پشتيباني اجراي همگام ميافزايد. كامپايلر همچنين دو متد ديگر را – BeginInvoke و EndInvoke – براي پشتيباني اجراي ناهمگام ميافزايد.
شما ميتوانيد مثال قبلي را با استفاده از يك شيء delegate براي اجراي متد GetCustomerList به صورت ناهمگام بازنويسي نماييد. به جاي فراخواني Invoke بر روي شيء delegate، BeginInvoke را فراخواني كنيد، كه مستلزم آن است كه شما هر پارامتر ورودي كه در امضاي فراخواني delegate تعريف شده است را انتقال دهيد. در مثال من، يك پارامتر رشتهاي واحد براي انتقال يك نام وضعيت وجود دارد.
'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute method asynchronously
handler1.BeginInvoke("CA", Nothing, Nothing)
BeginInvoke همچنين دو پارامتر ديگر دريافت ميكند كه بخشي از امضاي فراخواني Invoke نيستند. من اين دو پارامتر را بعدا توضيح خواهد داد.
هنگامي كه شما يك فراخواني به BeginInvoke بر روي يك شيء delegate انجام ميدهيد، شما در واقع از CLR (common language runtime)i درخواست ميكنيد كه اجراي متد اداره كننده را به صورت ناهمگام بر روي يك thread ثانويه آغاز كند. در عين حال كه اجراي يك متد به صورت ناهمگام با يك فراخواني به BeginInvoke قدرتمند است، آن آسان نيز هست زيرا شما مجبور نيستيد دغدغهاي در مورد ايجاد و اداره يك thread ثانويه داشته باشيد. CLR اين را به صورت خودكار براي شما انجام ميدهد.
تصوير 1 نحوه مواجهه CLR با يك فراخواني به BeginInvoke را نشان ميدهد. هنگامي كه شما متد BeginInvoke را فراخواني ميكنيد، شيء delegate يك تقاضا را در يك صف داخلي خاص قرار ميدهد. CLR مجموعهاي حاوي threadهاي كارگر (worker) را نگهداري ميكند كه مسئول سرويسدهي به تقاضاي موجود در اين صف هستند. اجراي ناهمگام محقق ميشود زيرا thread فراخواننده BeginInvoke با thread اجرا كننده متد اداره كننده يكسان نيست.
تصوير 1 – اجراي متد ناهمگام
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
CLR به صورت پويا با بررسي چند فاكتور تعيين مينمايد كه چه تعداد thread كارگر به مجموعه افزوده شوند. در يك برنامه معمولي دسكتاپ، CLR مجموعه را به تعدادي thread محدود ميكند. در يك برنامه پركار سمت سرور كه در آن صفحات كد ASP.NET تعداد زيادي فراخواني همزمان به BeginInvoke انجام ميدهند، CLR اندازه مجموعه را حداكثر تا 25 thread به ازاي هر پردازنده موجود بر روي ماشين ميزبان افزايش ميدهد. اين بدان معني است كه تعداد threadهاي كارگر موجود در مجموعه به نسبت تعداد پردازندهها افزايش پيدا ميكند. براي مثال، حداكثر اندازه مجموعه بر روي يك كامپيوتر سرور ميزبان با چهار پردازنده برابر با 100 thread است.
حالا تفاوتهاي ميان يك فراخواني به Invoke و يك فراخواني به BeginInvoke را بررسي ميكنيم. فرض كنيد شما ميخواهيد لباسهاي كثيف خود را كه در طي يك ماه گذشته جمع كردهايد تميز نماييد. شما ميتوانيد خودتان اين كار را با مراجعه به خشكشويي انجام دهيد يا انجام اين كار را به شخص ديگري محول كنيد.
اگر شما خودتان شخصا به خشكشويي برويد و لباس را در ماشين لباسشويي قرار دهيد كارتان شبيه فراخواني Invoke خواهد بود؛ اين يك كار همگام است. شما بايد در خشكشويي بمانيد تا كار تكميل شود و هنگامي كه آنجا را ترك ميكنيد لباسهاي تميز شده خود را نيز به همراه ميبريد.
تحويل لباسهاي كثيفتان به مسئول خشكشويي همانند يك فراخواني به BeginInvoke است؛ اين يك كار ناهمگام است. پس از آن كه لباسهاي كثيفتان را به مسئول خشكشويي تحويل داديد، شما ميتوانيد فورا به سراغ كارهاي ديگرتان برويد. اما، شما آنجا را به همراه لباسهاي تميز شدهتان ترك نميكنيد. شما آن محل را با يك قبض خشكشويي ترك ميكنيد و بايد در زمان ديگري براي دريافت لباسهاي تميزتان به آنجا باز گرديد. به علاوه، شما بايد مراقب قبض خود باشيد زيرا اگر آن را گم كنيد احتمالا مسئول خشكشويي از پس دادن لباسهاي شما امتناع خواهد نمود.
هنگامي كه شما BeginInvoke را فراخواني ميكنيد، شما در واقع يك درخواست براي اجراي يك متد در خشكشويي CLR تحويل ميدهيد. فراخواني به BeginInvoke بلافاصله باز ميگردد و thread فراخواننده سپس ميتواند به سراغ كارهاي ديگر خود برود بدون اين كه مجبور باشد در انتظار اجراي متد مورد نظر از سوي CLR باشد. پس از فراخواني BeginInvoke شما ميدانيد كه CLR قصد اجراي آن متد مورد نظر را بر روي thread ثانويه دارد، اما شما نميدانيد كه دقيقا چه هنگام اين اتفاق رخ خواهد داد. بعدا در زمان ديگري، شما ميتوانيد از CLR بپرسيد كه آيا اجراي آن متد را به صورت ناهمگام به پايان رسانده است. شما همچنين ميتوانيد مقدار بازگشتي متد را بازيابي كنيد. اما، مانيتورينگ وضعيت يك فراخواني ناهمگام يا به دست آوردن مقدار بازگشتي يك متد ناهمگام مستلزم آن است كه اطلاعاتي را در مورد درخواست ناهمگامي كه شما مشغول بررسي آن هستيد براي CLR فراهم كنيد.
IAsyncResult
يك فراخواني به BeginInvoke چيزي را برميگرداند كه معادل با يك قبض خشكشويي است. اگر بخواهيم دقيقتر بگوييم، متد BeginInvoke براي برگرداندن شيئي كه يك رابط بنام IAsyncResult را پيادهسازي ميكند طراحي شده است. اينجا مثالي از فراخواني BeginInvoke و دريافت شيء IAsyncResult (قبض خشكشويي) در هنگام ارسال يك فراخواني متد ناهمگام مشاهده ميكنيد:
'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute method asynchronously and capture IAsyncResult object
Dim ar As System.IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)
شيء IAsyncResult به شما امكان مانيتور كردن يك فراخواني ناهمگام در حال پيشروي را ميدهد. آن همچنين امكان بازيابي مقدار بازگشتي و هر پارامتر خروجي را در هنگام پايان يافتن اجراي يك متد ناهمگام براي شما فراهم ميآورد.
درك اين نكته از سوي شما اهميت دارد كه لزوما يك رابطه يك-به-يك ميان يك شيء delegate و يك شيء IAsyncResult وجود ندارد. براي مثال، يك شيء delegate ميتواند براي آغاز دو يا چندين فراخواني ناهمگام كه به صورت همزمان اجرا خواهند شد مورد استفاده قرار گيرد، به اين صورت:
'
*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList
'*** execute multiple methods asynchronously
Dim ar1, ar2 As System.IAsyncResult
ar1 = handler1.BeginInvoke("CA", Nothing, Nothing)
ar2 = handler1.BeginInvoke("WA", Nothing, Nothing)
تصوير 2
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
در اين مثال، دو فراخواني ناهمگام مختلف با هم آغاز ميشوند. هر كدام از اين فراخوانيهاي ناهمگام بر روي يك thread مجزا از مجموعه thread كارگر اجرا خواهند شد، چنان كه در تصوير 2 نشان داده شده است. اين ميتواند ارزشمند باشد چون شما ميتوانيد دو يا چندين فراخواني را به صورت موازي بر روي شبكه انجام دهيد. اما، يادآوري اين نكته براي شما حائز اهميت است كه هر يك از اين فراخوانيهاي ناهمگام داراي شيء IAsyncResult مرتبط با خود است. رابط IAsyncResult يك خصيصه بنام IsComplete را ارائه ميكند كه امكان مانيتور كردن وضعيت يك فراخواني ناهمگام و تعيين اين كه آيا آن تكميل شده است را براي شما فراهم ميآورد. در عين حال كه اين نوع تكنيك polling در اغلب طراحيها كارآمد نيست، آن ميتواند در برخي شرايط مفيد واقع شود. اين يك مثال از استفاده از خصيصه IsComplete براي تعيين اين كه آيا CLR اجراي يك فراخواني ناهمگام را به اتمام رسانده است، ميباشد:
Dim ar As IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)
'*** allow some time to pass
'*** check to see if the async call has completed
If (ar.IsCompleted) Then
'*** retrieve method return value
End If
فراخواني EndInvoke
شما با فراخواني متد EndInvoke بر روي delegate، مقادير بازگشتي و هر پارامتر خروجي را بازيابي ميكنيد. EndInvoke با همان نوع بازگشت مقدار بازگشتي امضاي فراخواني نوع delegate تعريف ميشود:
Dim ar As IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)
'*** allow some time to pass
Dim retval As String()
retval = handler1.EndInvoke(ar)
چنان كه مشاهده ميكنيد، فراخواني EndInvoke امكان بازيابي مقدار بازگشتي از يك فراخواني متد ناهمگام را به شما ميدهد. همچنين توجه داشته باشيد كه يك فراخواني به EndInvoke شما را ملزم به انتقال شيء IAsyncResult مرتبط با يك فراخواني ناهمگام خاص مينمايد. اين درست شبيه دادن قبضتان به مسئول خشكشويي هنگام بازگشتتان براي دريافت لباسهاي تميز است. شيء IAsyncResult اين امكان را براي CLR فراهم ميآورد كه تعيين نمايد كدام فراخواني ناهمگام مورد نظر شماست.
همچنين توجه داشته باشيد كه ليست پارامتر براي EndInvoke شامل هر پارامتر ByRef تعريف شده درون نوع delegate خواهد بود از اين رو شما همچنين ميتوانيد هر پارامتر خروجي را بازيابي نماييد. اما، مثالي كه من در اينجا مطرح كردهام دربردارندهي هيچ پارامتر خروجي نيست، بنابراين فراخواني به EndInvoke فقط مستلزم آن است كه شما يك پارامتر واحد نگهدارنده يك ارجاع به شيء IAsyncResult را انتقال دهيد.
شما ديديد كه يك فراخواني به EndInvoke اهميت دارد زيرا آن امكان بازيابي مقدار بازگشتي و هر پارامتر خروجي را به شما ميدهد. فراخواني EndInvoke همچنين حياتي است زيرا آن به شما اين امكان را ميدهد كه ببينيد آيا متد ناهمگام با موفقيت اجرا شده است. اگر متد ناهمگام با يك استثناء اداره نشده مواجه شود، آن شيء استثناء توسط CLR تحت نظر قرار ميگيرد و سپس هنگامي كه شما EndInvoke را فراخواني ميكنيد ايجاد ميگردد (throw ميشود)، چنان كه در كد زير نشان داده شده است:
Dim retval As String()
Try
retval = handler1.EndInvoke(ar)
Catch ex As Exception
'*** deal with exception here
End Try
مكمل كردن هر فراخواني به BeginInvoke با يك فراخواني به EndInvoke مهم است. اگر شما EndInvoke را فراخواني نكنيد، شما هرگز نميتوانيد واقعا اطمينان يابيد كه يك فراخواني ناهمگام با موفقيت اجرا شده است. فراموش كردن انجام فراخواني EndInvoke ميتواند باعث ناديده گرفته شدن استثناءهاي زمان اجرا شود، كه ميتواند باگهايي را در كد شما به وجود آورد كه يافتن و اصلاح آنها دشوار است.
شما تا اينجا چند دليل خوب براي اين سوال يافتيد كه چرا يك فراخواني به BeginInvoke نيازمند يك فراخواني به EndInvoke است. در اينجا يك دليل بسيار مهم ديگر براي اين سوال وجود دارد كه چرا شما بايد همواره EndInvoke را فراخواني كنيد – اين قانون است. فراموش كردن انجام فراخواني EndInvoke مانع از اين خواهد شد كه CLR برخي از منابع مورد نياز در انتقال فراخوانيهاي ناهمگام را به دست آورد. بنابراين، شما بايد اين گونه تصور كنيد كه اگر فراخوانيهايي را به BeginInvoke انجام دهيد بدون اين كه فراخوانيهاي مرتبطي را نيز به EndInvoke انجام دهيد برنامه شما نشت (leak) خواهد داشت.
حالا بياييد توجه خود را به نحوه عمل زمانبندي در زماني كه شما اقدام به فراخواني EndInvoke ميكنيد مبذول نماييم. يك فراخواني به EndInvoke در صورتي كه thread كارگر اجراي متد اداره كننده را تكميل كرده باشد بلافاصله باز ميگردد. اما، يك فراخواني به EndInvoke در صورتي كه فراخواني ناهمگام هنوز آغاز نشده باشد يا هنوز در حال انجام باشد بلوك خواهد شد. شما در هنگام طراحي برنامههاي خود بايد به پيآمدهاي اين واقعيت توجه داشته باشيد.
وضعيتي را در نظر بگيريد كه در آن شما كد مورد نياز براي يك صفحه ASP.NET را نوشتهايد و خواهان اجراي دو فراخواني به صورت همزمان بر روي شبكه هستيد. اين واقعيت كه EndInvoke يك فراخواني بلوك كننده است هماهنگ كردن چندين مسير اجرا را بسيار تسهيل مينمايد. كد موجود در تصوير 3 دو متد را به صورت همزمان و ناهمگام اجرا ميكند تا دو فراخواني شبكه را به صورت موازي انجام دهد. اما، اين مهم است كه هر دو فراخواني شبكه قبل از آن كه تقاضا بتواند به صورت كامل به پايان برسد با موفقيت تكميل ميشوند. اولين فراخواني به EndInvoke بلوك ميشود تا اولين متد ناهمگام تكميل گردد. فراخواني دوم به متد EndInvoke بلوك ميشود تا دومين فراخواني تكميل گردد.
اگر دومين فراخواني ناهمگام قبل از اولين فراخواني ناهمگام به اتمام برسد چه اتفاقي ميافتد؟ اين به هيچ وجه اهميت ندارد. در اين طراحي اهميتي ندارد كه كدام يك از دو فراخواني شبكه ابتدا به پايان ميرسد. هر دو فراخواني به GetCustomerList بايد قبل از آن كه تقاضاي سمت سرور بتواند كار خود را به اتمام برساند تكميل شوند. بنابراين، رفتار بلوك كننده EndInvoke نقش مهمي را ايفا مينمايد. آن چند مسير اجرا را دريافت ميكند و آنها را براي ايجاد يك مسير منطقي اجرا به يكديگر ملحق مينمايد.
در تمام مدت، CLR همهي كارهاي پيچيده پشت صحنه را انجام ميدهد. آن threadها را مديريت ميكند، فراخوانيهاي ناهمگام را انتقال ميدهد، و threadهاي معيني را بلوك مينمايد تا ساير threadها كارشان را تكميل نمايند. تمام كاري كه شما بايد انجام دهيد فراخواني BeginInvoke و سپس فراخواني EndInvoke است. شما ميتوانيد آشكارا ببينيد كه delegateها يك ايده قدرتمند آسان را براي اجراي متد ناهمگام فراهم ميآورند.
حالا بياييد توجه خود را به اجراي متد ناهمگام در يك برنامه Windows Forms در حال اجرا بر روي يك برنامه دسكتاپ مبذول نماييم. اين يك سناريوي كاملا متفاوت از يك برنامه سمت سرور است زيرا اينجا تمامي كد معمولا بر روي يك thread واحد كه تحت عنوان UI thread اوليه شناخته ميشود اجرا ميگردد. UI thread اوليه در يك برنامه Windows Forms UI اهميت زيادي دارد زيرا آن مسئول حفظ و اداره پاسخگويي رابط كاربر است. اگر شما رابط كاربر اوليه را با يك فراخواني طويلالاجرا بر روي شبكه متوقف سازيد، برنامه ميزبان تا بازگشت فراخواني پاسخگوي كاربر نخواهد بود.
در اين حالت استفاده از اجراي ناهمگام حياتي است تا شما موجب توقف رابط كاربر نشويد. شما ديديد كه ارسال يك فراخواني ناهمگام با استفاده از متد BeginInvoke بسيار ساده است، اما يك مشكل وجود دارد. هنگامي كه يك فراخواني متد ناهمگام به اجرا پايان داد برنامه شما چگونه واكنش نشان ميدهد؟ هنگامي كه يك فراخواني ناهمگام خاتمه يافت شما چگونه رابط كاربر را به روز رساني ميكنيد؟ شما ميتوانيد از يك تكنيك polling استفاده كنيد و پيوسته تست تكميل انجام دهيد، اما اين زياد كارآمد نيست. در عوض، شما بايد از يك ويژگي مفيد delegateها بهره ببريد كه امكان برپا كردن يك متد بازخواني كه هنگام تكميل شدن يك فراخواني متد ناهمگام به صورت خودكار توسط CLR اجرا ميشود را براي شما فراهم ميكند.
كد موجود در تصوير 4 مثالي از يك برنامه Windows Forms را نشان ميدهد كه متد GetCustomerList را به صورت ناهمگام اجرا ميكند. تمامي كد درون يك كلاس بنام Form1 نوشته شده است. درون اين تعريف كلاس، شما ميتوانيد يك رويكرد ممكن براي برپايي يك متد بازخواني در هنگام انتقال يك فراخواني متدناهمگام با استفاده از BeginInvoke را مشاهده كنيد.
بياييد اين كد را بررسي كنيم. هنگامي كه شما خواهان برپايي يك متد بازخواني هستيد، شما بايد با يك نوع delegate بنام AsyncCallback كار كنيد كه توسط Framework Class Library در فضانام System تعريف شده است. هنگامي كه شما خواهان ايجاد يك متد بازخواني هستيد، بايد اين متد را با يك امضاي فراخواني تعريف كنيد كه با امضاي فراخواني نوع AsyncCallback delegate مطابقت داشته باشد. MyCallbackMethod در تصوير 4 يك مثال از يك متد تعريف شده با امضاي فراخواني مناسب است. چنان كه مشاهده ميكنيد، متد بازخواني بايد به عنوان يك رويهي Sub با يك پارامتر واحد از نوع IAsyncResult تعريف شود.
شما ميبينيد كه كلاس Form1 حاوي دو فيلد است، كه هر كدام يك ارجاع به يك شيء delegate را نگه ميدارند. فيلد اول، TargetHandler، يك ارجاع به يك شيء delegate نگه ميدارد كه براي انتقال فراخواني ناهمگام به متد GetCustomerList به كار برده ميشود. فيلد دوم، CallbackHandler، يك ارجاع به يك شيء delegate نگه ميدارد كه به MyCallbackMethod مقيد است. اين اشياء delegate هر دو در فراخواني به BeginInvoke به كار برده ميشوند.
حالا فراخواني به BeginInvoke درون متد اداره كننده رويداد cmdExecuteTask را مورد بررسي قرار دهيم. فيلد TargetHandler براي اجراي BeginInvoke و انتقال فراخواني ناهمگام مورد استفاده قرار ميگيرد. پارامتر دوم انتقال يافته به BeginInvoke، ارجاع به شيء delegateي است كه به MyCallbackMethod مقيد ميباشد. در واقع، شما يك ارجاع را به يك delegate انتقال ميدهيد تا به CLR بگوييد كه خواهان استفاده از كدام متد بازخواني هستيد. اين همه آن چيزي است كه براي برپايي يك متد بازخواني پس از يك فراخواني متد ناهمگام مورد نياز است.
حالا بياييد ببينيم هنگامي كه يك فراخواني ناهمگام انتقال مييابد اجزاء مثال من چگونه عمل مينمايند. هنگامي كه برنامه BeginInvoke را فراخواني ميكند، CLR متد اداره كننده را با استفاده از يك thread كارگر از مجموعه thread متعلق به CLR اجرا ميكند. سپس، همان thread كارگر متد بازخواني را اجرا مينمايد. هنگامي كه اين كار كامل شد، CLR آن thread كارگر را به مجموعه باز ميگرداند تا آن براي سرويسدهي به تقاضاهاي ناهمگام ديگر در دسترس باشد.
درك اين نكته بسيار حائز اهميت است كه متد بازخواني بر روي UI thread اوليهاي كه فراخواني به BeginInvoke را انجام داده است اجرا نميشود. يك بار ديگر هم ميگوييم كه متد بازخواني بر روي همان thread ثانويهاي كه متد ناهمگام را اجرا كرده است اجرا ميشود.
آخرين پارامتر انتقال يافته در يك فراخواني به BeginInvoke، پارامتر AsyncState است. AsyncState يك پارامتر بسيار بي محدوديت است كه شما ميتوانيد از آن براي انتقال هر چيزي كه ميخواهيد استفاده كنيد. آن بر اساس كلاس Object تعريف شده است از اين رو ميتواند براي انتقال هر مقدار يا شيئي به كار برده شود. در مثالي كه من در تصوير 4 ارائه نمودهام، هيچ نيازي به استفاده از پارامتر AsyncState نبود، به همين دليل يك مقدار Nothing انتقال يافت.
در ساير طراحيها، استفاده از پارامتر AsyncState ميتواند براي انتقال يك مقدار يا يك شيء از كدي كه BeginInvoke را فراخواني ميكند به پيادهسازي متد بازخواني مناسب باشد. براي مثال، فرض كنيد كه شما خواهان انتقال يك Integer هستيد تا در پيادهسازي متد بازخواني در دسترس قرار گيرد. هنگامي كه شما BeginInvoke را فراخواني ميكنيد، شما ميتوانيد مقدار Integer را در پارامتر AsyncState انتقال دهيد، به اين صورت:
Dim MyValue As Integer = 100
TargetHandler.BeginInvoke("CA", CallbackHandler, MyValue)
مقدار يا شيئي كه در شيء AsyncState انتقال يافته توسط شيء IAsyncResult پيگيري ميشود ميتواند با استفاده از يك خصيصه بنام AsyncState بازيابي گردد. اينجا شاهد مثالي از بازيابي مقدار Integer در يك متد بازخواني هستيد:
Sub MyCallbackMethod(ByVal ar As IAsyncResult)
Dim y As Integer = CInt(ar.AsyncState)
'*** other code omitted for brevity
End Sub
به ياد داشته باشيد كه خصيصه AsyncState بر اساس كلاس Object تعريف شده است، از اين رو شما معمولا بايد آن را صريحا به يك نوع خاصتر تبديل كنيد قبل از آن كه بتوانيد با مقدار يا شيئي كه درون آن است برنامه بنويسيد.
شما ديديد كه فراخواني BeginInvoke از كد متعلق به يك Windows Form براي انتقال يك فراخواني متد ناهمگام نسبتا آسان است. شما همچنين آموختيد كه چگونه ميتوان يك متد بازخواني را كه در هنگام تكميل شدن فراخواني متد ناهمگام به صورت خودكار عمل مينمايد برپا كرد. آخرين چيزي كه شما بايد بياموزيد نحوه به روز رساني UI است تا به كاربر بگوييد كه كار فراخواني ناهمگام پايان يافته است. اين ميتواند دشوار باشد. نوشتن كد به شكلي درست دشوار است و اگر شما اين كار را به درستي انجام ندهيد احتمالا با مشكلات جدي روبرو خواهيد شد.
يك قاعده threading مهم وجود دارد كه بايد در هنگام برنامهنويسي با Windows Forms از آن پيروي كرد. UI thread اوليه تنها threadي است كه مجاز به دسترسي به يك شيء فرم و تمامي كنترلهاي فرزند آن است. اين بدان معني است كه كدي كه بر روي يك thread ثانويه اجرا ميشود مجاز به دسترسي به متدها و خصيصههاي يك فرم يا يك كنترل نيست. اما، شما بايد به خاطر داشته باشيد كه يك متد بازخواني از قبيل MyCallbackMethod بر روي يك thread ثانويه اجرا ميشود نه بر روي UI thread اوليه. اين بدان معني است كه شما هرگز نبايد مستقيما از اين نوع متد بازخواني مبادرت به بروزرساني رابط كاربر نماييد.
به عبارت ديگر، متد بازخواني كه بر روي thread ثانويه اجرا ميشود بايد UI thread اوليه را وادار به اجراي كدي نمايد كه رابط كاربر را به روز رساني كند. كد مورد نياز براي انجام اين كار نوشته شده است و من در قسمت بعدي مقاله دقيقا شرح خواهم داد كه چگونه اين كار را انجام دهيد. فعلا فقط اين را بدانيد كه به روز رساني رابط كاربر مستقيا از يك متد بازخواني همچون MyCallbackMethod به دليل محدوديتهاي threading اعمال شده از جانب framework يك تكنيك برنامهنويسي قابل قبول نيست.
نتيجه
در هنگام اجراي يك فراخواني متد ناهمگام با استفاده از يك delegate، هر نوع delegate يك متد BeginInvoke را براي آغاز يك فراخواني متد به صورت ناهمگام بر روي يك thread از يك مجموعه thread نگهداري شده توسط CLR فراهم ميآورد. هر نوع delegate متد EndInvoke مكمل را فراهم مينمايد كه ميتواند براي به دست آوردن مقدار بازگشتي يك متد ناهمگام و براي تعيين اين كه آيا متد بدون هيچ استثناء اداره نشدهاي اجرا شده است مورد استفاده قرار گيرد.
يكي از ارزشمندترين جنبههاي اجراي ناهمگام با استفاده از delegate ها اين است كه لازم نيست شما دغدغهاي در مورد ايجاد و اداره threadهاي ثانويه داشته باشيد. CLR به صورت خودكار يك مجموعه از threadهاي كارگر را براي شما نگهداري ميكند، كه اين بدان معني است كه شما ميتوانيد از تكنيكهايي شامل اجراي متد ناهمگام و موازي بدون نياز به برنامهنويسي مستقيم با threadها بهره ببريد. كل چيزي كه براي آموختن باقي ميماند نحوه فراخواني BeginInvoke و EndInvoke در زمانهاي مناسب است.
تصوير 3 – اجراي چندين متد به صورت ناهمگام.
'
*** server-side request in code behind an ASP.NET page
'*** execute multiple methods asynchronously
Dim ar1, ar2 As System.IAsyncResult
ar1 = handler1.BeginInvoke("CA", Nothing, Nothing)
ar2 = handler1.BeginInvoke("WA", Nothing, Nothing)
'*** capture all return values
Dim retval1, retval2 As String()
retval1 = handler1.EndInvoke(ar1)
retval2 = handler1.EndInvoke(ar2)
'*** finish processing request
تصوير 4 – متد بازخواني.
Public Class Form1 : Inherits System.Windows.Forms.Form
Private TargetHandler As GetCustomerListHandler _ = AddressOf DataAccessCode.GetCustomerList
Private CallbackHandler As AsyncCallback _
= AddressOf MyCallbackMethod
Sub cmdExecuteTask_Click(sender As Object, e As EventArgs) _
Handles cmdExecuteTask.Click
'*** execute method asynchronously with callback
TargetHandler.BeginInvoke("CA", CallbackHandler, Nothing)
End Sub
'*** callback method runs on worker thread and not the UI thread
Sub MyCallbackMethod(ByVal ar As IAsyncResult)
'*** this code fires at completion of each asynchronous method call
Dim retval As String()
retval = TargetHandler.EndInvoke(ar)
End Sub
End Class
منبع:سايت ماهنامه علم الكترونيك و كامپيوتر