سه شنبه ۲۷ شهريور ۱۴۰۳
Tut24 آموزش برنامه نویسی و مجله تخصصی فناوری ورود/عضویت

آموزش سی شارپ - چندنخی

نخ (Thread) به عنوان مسیر اجرایی یک برنامه تعریف می‌شود. هر نخ، یک جریان کنترل منحصر به فرد را تعریف می‌کند. اگر برنامه شما شامل عملیات‌های پیچیده و زمان‌بر باشد، معمولاً بهتر است مسیرها یا نخ‌های اجرا را با انجام کار خاصی برای هر نخ تنظیم کنید.

نخ‌ها فرایندهای سبک هستند. یک مثال معمول از استفاده از نخ‌ها، پیاده‌سازی برنامه‌نویسی همروند توسط سیستم‌عامل‌های مدرن است. استفاده از نخ‌ها، تلفات چرخه CPU را کاهش می‌دهد و کارایی برنامه را افزایش می‌دهد.

تاکنون برنامه‌هایی را نوشته‌ایم که یک نخ تنها به عنوان یک فرآیند تکرار می‌شود که نمونه در حال اجرای برنامه است. با این روش، برنامه فقط می‌تواند یک کار را همزمان انجام دهد. برای اجرای بیش از یک وظیفه در یک زمان، می‌توان آن را به نخ‌های کوچکتر تقسیم کرد.

چرخه عمر نخ

چرخه عمر یک نخ زمانی شروع می‌شود که یک شیء از کلاس System.Threading.Thread ایجاد می‌شود و در پایان زمانی که نخ خاتمه یا اجرایش را تکمیل می‌کند، به پایان می‌رسد.

در زیر، وضعیت‌های مختلف در چرخهٔ عمر یک نخ آمده است:

  • وضعیت آغاز نشده (Unstarted State) - وقتی نمونهٔ نخ ایجاد می‌شود ولی متد Start فراخوانی نشده است.

  • وضعیت آماده (Ready State) - وقتی نخ آماده برای اجرا است و در انتظار دورهٔ CPU است.

  • وضعیت غیرقابل اجرا (Not Runnable State) - نخ غیرقابل اجرا است زمانی که:

    • متد Sleep فراخوانی شده است
    • متد Wait فراخوانی شده است
    • باعث مسدود شدن در عملیات I/O شده است
  • وضعیت خاتمه (Dead State) - وقتی نخ اجرا را به پایان می‌رساند یا به خاطر لغو شدن خاتمه می‌یابد.

نخ اصلی (The Main Thread)

در سی شارپ، از کلاس System.Threading.Thread برای کار با نخ ها استفاده می‌شود. این کلاس ایجاد و دسترسی به نخ های جداگانه در یک برنامه مولتی‌ترد را امکان‌پذیر می‌کند. اولین نخی که در یک فرآیند اجرا می‌شود، نخ اصلی نامیده می‌شود.

وقتی یک برنامه سی شارپ اجرا می‌شود، نخ اصلی به صورت خودکار ایجاد می‌شود. نخ های ایجاد شده با استفاده از کلاس Thread، نخ های فرزند نخ اصلی نامیده می‌شوند. شما می‌توانید با استفاده از ویژگی CurrentThread کلاس Thread به یک نخ دسترسی پیدا کنید.

برنامه زیر اجرای نخ اصلی را نشان می‌دهد:

using System;
using System.Threading;

namespace MultithreadingApplication {
   class MainThreadProgram {
      static void Main(string[] args) {
         Thread th = Thread.CurrentThread;
         th.Name = "MainThread";
         
         Console.WriteLine("This is {0}", th.Name);
         Console.ReadKey();
      }
   }
}

هنگامی که کد بالا کامپایل و اجرا می شود، نتیجه زیر را ایجاد می کند -

This is MainThread

ویژگی‌ها و متدهای کلاس نخ (Thread)

جدول زیر ویژگی‌های پرکاربرد کلاس نخ (Thread) را نشان می‌دهد −

ردیف ویژگی و توضیحات
1

CurrentContext

محیط کنونی که نخ در آن اجرا می‌شود را برمی‌گرداند.

2

CurrentCulture

فرهنگ مربوط به نخ کنونی را دریافت یا تنظیم می‌کند.

3

CurrentPrincipal

شیء اصلی فعلی نخ را (برای امنیت مبتنی بر نقش) دریافت یا تنظیم می‌کند.

4

CurrentThread

نخی را که در حال اجرا است، برمی‌گرداند.

5

CurrentUICulture

فرهنگ کنونی که مدیر منابع در زمان اجرا از آن استفاده می‌کند را دریافت یا تنظیم می‌کند.

6

ExecutionContext

شیء ExecutionContext را دریافت می‌کند که شامل اطلاعاتی درباره محیط‌های مختلف نخ کنونی است.

7

IsAlive

مقداری را برمی‌گرداند که وضعیت اجرایی نخ کنونی را نشان می‌دهد.

8

IsBackground

مقداری را برمی‌گرداند که نشان دهنده این است که آیا یک نخ، نخ پس‌زمینه است یا نه.

9

IsThreadPoolThread

مقداری را برمی‌گرداند که نشان دهنده این است که آیا یک نخ، به استخر نخ‌های مدیریت شده تعلق دارد یا نه.

10

ManagedThreadId

شناسه‌ی منحصر به فرد برای نخ مدیریت شده کنونی را برمی‌گرداند.

11

Name

نام نخ را دریافت یا تنظیم می‌کند.

12

Priority

مقداری را برمی‌گرداند که نشان دهنده اولویت زمانبندی یک نخ است.

13

ThreadState

مقداری را برمی‌گرداند که شامل وضعیت‌های نخ کنونی است.

جدول زیر متدهای پرکاربرد کلاس نخ (Thread) را نشان می‌دهد −

ردیف متد و توضیحات
1

public void Abort()

یک استثناء ThreadAbortException در نخی که بر روی آن فراخوانی می‌شود، پرتاب می‌کند تا فرآیندی برای پایان دادن به نخ شروع شود. فراخوانی این متد به طور معمول منجر به پایان دادن نخ می‌شود.

2

public static LocalDataStoreSlot AllocateDataSlot()

یک اسلات داده بدون نام را در همه نخ‌ها تخصیص می‌دهد. برای بهبود عملکرد، از فیلدهایی استفاده کنید که با ویژگی ThreadStaticAttribute علامت گذاری شده‌اند.

3

public static LocalDataStoreSlot AllocateNamedDataSlot(string name)

یک اسلات داده با نام را در همه نخ‌ها تخصیص می‌دهد. برای بهبود عملکرد، از فیلدهایی استفاده کنید که با ویژگی ThreadStaticAttribute علامت گذاری شده‌اند.

4

public static void BeginCriticalRegion()

به میزبان اعلام می‌کند که اجرا به یک ناحیه کدی وارد می‌شود که اثرات یک خطا یا استثناء ناپایدار ممکن است وظایف دیگری در دامنه برنامه را به خطر بیندازد.

5

public static void BeginThreadAffinity()

به میزبان اعلام می‌کند که کد مدیریت شده در حال اجرای دستوراتی است که به هویت نخ فیزیکی فعلی سیستم عامل وابسته است.

6

public static void EndCriticalRegion()

به میزبان اعلام می‌کند که اجرا به یک ناحیه کدی وارد می‌شود که اثرات یک خطا یا استثناء ناپایدار تا وظیفه کنونی محدود می‌شود.

7

public static void EndThreadAffinity()

به میزبان اعلام می‌کند که کد مدیریت شده به اتمام رسیده است که دستورات وابسته به هویت نخ فیزیکی فعلی سیستم عامل را اجرا می‌کند.

8

public static void FreeNamedDataSlot(string name)

ارتباط بین یک نام و اسلات را برای تمام نخ‌ها در فرآیند حذف می‌کند. برای بهبود عملکرد، از فیلدهایی استفاده کنید که با ویژگی ThreadStaticAttribute علامت گذاری شده‌اند.

9

public static Object GetData(LocalDataStoreSlot slot)

مقدار را از اسلات مشخص شده در نخ کنونی و دامنه کنونی نخ استخراج می‌کند. برای بهبود عملکرد، از فیلدهایی استفاده کنید که با ویژگی ThreadStaticAttribute علامت گذاری شده‌اند.

10

public static AppDomain GetDomain()

دامنه فعلی که نخ کنونی در آن اجرا می‌شود را برمی‌گرداند.

11

public static AppDomain GetDomainID()

شناسه‌ی یکتای دامنه برنامه را برمی‌گرداند.

12

public static LocalDataStoreSlot GetNamedDataSlot(string name)

یک اسلات داده با نام را جستجو می‌کند. برای بهبود عملکرد، از فیلدهایی استفاده کنید که با ویژگی ThreadStaticAttribute علامت گذاری شده‌اند.

13

public void Interrupt()

یک نخ را که در حالت WaitSleepJoin است، وقفه می‌دهد.

14

public void Join()

نخ فراخواننده را مسدود می‌کند تا یک نخ خاتمه یابد و در عین حال به ادامه عملیات‌های COM و SendMessage بپردازد. این متد شکل‌های Overload مختلفی دارد.

15

public static void MemoryBarrier()

دسترسی به حافظه را هماهنگ می‌کند: پردازنده‌ای که نخ کنونی را اجرا می‌کند، دستورات را به گونه‌ای نمی‌تواند مرتب کند که دسترسی‌های حافظه قبل از فراخوانی MemoryBarrier پس از دسترسی‌های حافظه‌ای که بعد از فراخوانی MemoryBarrier صورت می‌گیرد، اجرا شود.

16

public static void ResetAbort()

یک درخواست Abort درخواست شده برای نخ کنونی را لغو می‌کند.

17

public static void SetData(LocalDataStoreSlot slot, Object data)

داده را در اسلات مشخص شده برای نخی که در دامنه‌ی کنونی خود اجرا می‌شود، تنظیم می‌کند. برای بهبود عملکرد، از فیلدهایی استفاده کنید که با ویژگی ThreadStaticAttribute علامت گذاری شده‌اند.

18

public void Start()

یک نخ را شروع می‌کند.

19

public static void Sleep(int millisecondsTimeout)

نخ را به مدت زمان مشخص شده متوقف می‌کند.

20

public static void SpinWait(int iterations)

باعث می‌شود یک نخ تعداد مشخصی بار منتظر بماند که توسط پارامتر iterations تعیین می‌شود.

21

public static byte VolatileRead(ref byte address)

public static double VolatileRead(ref double address)

public static int VolatileRead(ref int address)

public static Object VolatileRead(ref Object address)

مقدار یک فیلد را خوانده و بازگردانی می‌کند. این مقدار، آخرین مقداری است که توسط هر پردازنده‌ای در یک رایانه نوشته شده است، بدون در نظر گرفتن تعداد پردازنده‌ها یا وضعیت حافظه نهان پردازنده. این متد شکل‌های Overload مختلفی دارد. تنها برخی از آن‌ها در بالا ذکر شده‌اند.

22

public static void VolatileWrite(ref byte address, byte value)

public static void VolatileWrite(ref double address, double value)

public static void VolatileWrite(ref int address, int value)

public static void VolatileWrite(ref Object address, Object value)

مقداری را به یک فیلد بلافاصله نوشته و تضمین می‌کند که این مقدار برای تمام پردازنده‌ها در رایانه قابل مشاهده باشد. این متد شکل‌های Overload مختلفی دارد. تنها برخی از آن‌ها در بالا ذکر شده‌اند.

23

public static bool Yield()

باعث می‌شود نخ فراخواننده اجرا را به یک نخ دیگر که برای اجرا آماده است، واگذار کند. سیستم عامل نخی را که باید اجرا شود، انتخاب می‌کند.

ایجاد نخ‌ها

نخ‌ها با گسترش کلاس Thread ایجاد می‌شوند. سپس کلاس Thread گسترش یافته، متد Start() را صدا می‌زند تا اجرای نخ فرزند آغاز شود.

برنامه زیر مفهوم را نشان می‌دهد −

using System;
using System.Threading;

namespace MultithreadingApplication {
   class ThreadCreationProgram {
      public static void CallToChildThread() {
         Console.WriteLine("Child thread starts");
      }
      static void Main(string[] args) {
         ThreadStart childref = new ThreadStart(CallToChildThread);
         Console.WriteLine("In Main: Creating the Child thread");
         Thread childThread = new Thread(childref);
         childThread.Start();
         Console.ReadKey();
      }
   }
}

هنگامی که کد بالا کامپایل و اجرا می شود، نتیجه زیر را ایجاد می کند -

In Main: Creating the Child thread
Child thread starts

مدیریت نخ‌ها

کلاس Thread روش‌های مختلفی برای مدیریت نخ‌ها فراهم می‌کند.

مثال زیر استفاده از متد sleep() را برای توقف نخ به مدت زمان مشخص نشان می‌دهد.

using System;
using System.Threading;

namespace MultithreadingApplication {
   class ThreadCreationProgram {
      public static void CallToChildThread() {
         Console.WriteLine("Child thread starts");
         
         // the thread is paused for 5000 milliseconds
         int sleepfor = 5000; 
         
         Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
         Thread.Sleep(sleepfor);
         Console.WriteLine("Child thread resumes");
      }
      
      static void Main(string[] args) {
         ThreadStart childref = new ThreadStart(CallToChildThread);
         Console.WriteLine("In Main: Creating the Child thread");
         
         Thread childThread = new Thread(childref);
         childThread.Start();
         Console.ReadKey();
      }
   }
}

هنگامی که کد بالا کامپایل و اجرا می شود، نتیجه زیر را ایجاد می کند -

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
Child thread resumes

از بین بردن نخ‌ها

متد Abort() برای از بین بردن نخ‌ها استفاده می‌شود.

زمان اجرا با پرتاب یک ThreadAbortException، نخ را از بین می‌برد. این استثنا قابل دریافت نیست و در صورت وجود، کنترل به بخش finally منتقل می‌شود.

برنامه زیر این موضوع را نمایش می‌دهد −

using System;
using System.Threading;

namespace MultithreadingApplication {
   class ThreadCreationProgram {
      public static void CallToChildThread() {
         try {
            Console.WriteLine("Child thread starts");
            
            // do some work, like counting to 10
            for (int counter = 0; counter <= 10; counter++) {
               Thread.Sleep(500);
               Console.WriteLine(counter);
            }
            
            Console.WriteLine("Child Thread Completed");
         } catch (ThreadAbortException e) {
            Console.WriteLine("Thread Abort Exception");
         } finally {
            Console.WriteLine("Couldn't catch the Thread Exception");
         }
      }
      static void Main(string[] args) {
         ThreadStart childref = new ThreadStart(CallToChildThread);
         Console.WriteLine("In Main: Creating the Child thread");
         
         Thread childThread = new Thread(childref);
         childThread.Start();
         
         //stop the main thread for some time
         Thread.Sleep(2000);
         
         //now abort the child
         Console.WriteLine("In Main: Aborting the Child thread");
         
         childThread.Abort();
         Console.ReadKey();
      }
   }
}

هنگامی که کد بالا کامپایل و اجرا می شود، نتیجه زیر را ایجاد می کند -

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception