۲۹ - ۹استثناهای آسنکرون

استثناهای آسنکرون*، شکارچیِ برنامه‌های خوشحال و کوچولو اند. احتمالاً تجربه‌ی زیادی با اونها ندارین، مگر اینکه قبلاً با اِرلنگ کار کرده باشین. البته استثناهای آسنکرون ِ ارلنگ در یه 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‎‏ ِه) کارِش تموم بشه. به خاطرِ اینکه آخرین کاری که ریسه‌ی محاسباتی ِ فرزند انجام میده، انتهای اون ماسک ِه، استثنایی که ریسه‌ی محاسباتی ِ اصلی سعی می‌کنه بندازه به ریسه‌ی محاسباتی ِ فرزند، توی صورتِ خودش می‌ترکه، و در نتیجه توی ریسه‌ی محاسباتی ِ اصلی انداخته میشه.