۲۹ - ۹استثناهای آسنکرون
استثناهای آسنکرون*، شکارچیِ برنامههای خوشحال و کوچولو اند. احتمالاً تجربهی زیادی با اونها ندارین، مگر اینکه قبلاً با اِرلنگ کار کرده باشین. البته استثناهای آسنکرون ِ ارلنگ در یه process ِ دیگه هندل میشن. بیشترِ زبانها اصلاً چیزی مشابهِ این ندارن.
module Main module
-- .این رو توضیح ندادیم
-- .سخت اند
import Control.Concurrent
(forkIO, threadDelay)
import Control.Exception
import System.IO
openAndWrite :: IO ()
openAndWrite = do
h <- openFile "test.dat" WriteMode
threadDelay 1500
hPutStr h
(replicate 100000000 '0' ++ "abc")
hClose h
data PleaseDie =
PleaseDie
deriving Show
instance Exception PleaseDie
main :: IO ()
main = do
threadId <- forkIO openAndWrite
threadDelay 1000
throwTo threadId PleaseDie
-- ^----^
-- م. بنداز به
م. یا استثناهای غیرهمزمان
قراره که با اجرای این برنامه یه فایلی به اسمِ test.dat
درست بشه که همهش صفره، و به “abc” در انتها نرسیده. آینده رو نمیشه پیشبینی کرد، اگه دیسکی دارین که سرعتِ خارقالعادهای در I/O داره، آرگومانِ replicate
رو بزرگتر کنین تا ایرادی که مدِ نظر هست رو بازسازی کنه. اگه خراب نیست، خرابش کنین!
اتفاقی که افتاد این بود که یه استثنای آسنکرون از ریسهی محاسباتی ِ اصلی به ریسهی محاسباتی ِ فرزند انداختیم، که باعث شد برنامه وسطِ کارش اتصال-کوتاه شه. اگه این کار رو وسطِ یه حلقه میکردین، دستگیره ِ فایلها رو هم نشت میدادین. اگه نشتی ِ دستگیره ِ فایلها بصورتِ مستمر در یه بازهی زمانی ادامه پیدا کنه، منجر به مردنِ برنامه یا بیثباتیِ کامپیوتر میشه.
استثناهای آسنکرون رو میشه استثناهایی دونست که در یه ریسهی محاسباتی ِ دیگه، غیر از اونی که خطا رو دریافت کرده، عَلَم میشن. خیلی پرفایدهاند، و کمک میکنن در زبانهای دیگهای که رسماً استثناهای آسنکرون ندارن، در موردِ شرایطِ خطا صحبت کنیم. در هر زبانی، سیستم عامل ممکنه یهو برنامه رو بکُشه. اتفاقاً ما هم همین قابلیت رو از داخلِ خودِ زبان برنامهنویسی، در سطحِ ریسهی محاسباتی داریم. مسئله اینجاست که میخوایم موقتاً استثناها رو تا زمانی که کاری که داریم انجام میدیم تموم نشده، نادیده بگیریم. دلیلِ این کار، علاوه بر اینکه میخوایم حالتِ فایل درست و مناسب باشه، اینه که منابعی مثلِ دستگیرههای فایل، یا ارتباطاتِ پایگاهِ داده، یا چیزهای مشابهِ دیگه رو نشت ندیم.* ولی نترسین، ما میتونیم درستش کنیم!
module Main module
import Control.Concurrent (forkIO, threadDelay)
import Control.Exception
import System.IO
openAndWrite :: IO ()
openAndWrite = do
h <- openFile "test.dat" AppendMode
threadDelay 1500
hPutStr h
(replicate 100000000 '0' ++ "abc")
hClose h
data PleaseDie =
PleaseDie
deriving Show
instance Exception PleaseDie
main :: IO ()
main = do
threadId <- forkIO (mask_ openAndWrite)
threadDelay 1000
throwTo threadId PleaseDie
اینجا منظور از نشت کردن، اینه که چیزهای زیادی (فایل، ارتباطاتِ پایگاهِ داده و غیره) همزمان با هم باز باشن، و متعاقباً همهی منابعی که سیستم عامل میتونه در اختیار بذاره رو مصرف کنن. نگه داشتنِ چیزهای زیاد در حافظه برای مدت طولانی باعثِ نشتِ حافظه میشه.
اینجا از mask_
(از ماژول ِ Control.Exception
) استفاده کردیم تا استثناهایی که به ریسهی محاسباتی ِ فرزند انداخته میشن رو ماسکه کنیم، یا به تعویق بندازیم تا openAndWrite
(که یه اجراییه ِ IO
ِه) کارِش تموم بشه. به خاطرِ اینکه آخرین کاری که ریسهی محاسباتی ِ فرزند انجام میده، انتهای اون ماسک ِه، استثنایی که ریسهی محاسباتی ِ اصلی سعی میکنه بندازه به ریسهی محاسباتی ِ فرزند، توی صورتِ خودش میترکه، و در نتیجه توی ریسهی محاسباتی ِ اصلی انداخته میشه.