24 กุมภาพันธ์ 2552

Thread

Thread
Thread คือ สิ่งที่ทำงานโปรแกรมได้ เราสามารถเรียกดู Thread ได้ใน Windows Task Managerโดยเปิด Windows Task Manager ขึ้นมาแล้วไปที่ View->Select Column แล้วเลือกตรง
Thread Count จากนั้นคลิกที่ปุ่ม OK จากนั้นมาที่ แถบ Processes จะมี Thread เกิดขึ้นมา
การสร้าง Thread
เราสามารถสร้าง Thread โดยใช้คลาสที่ชื่อว่า Thread ที่อยู่ใน System.Threading โดยคอนสตรักเตอร์ของคลาส Thread ไดแก่
Thread t =new Thread(ParameterizedThreadStart start);
Thread t =new Thread(ThreadStart start);
Thread t =new Thread(ParameterizedThreadStart start),int maxStackSize);
Thread t =new Thread(ThreadStart start,int maxStackSize);
โดย ThreadStart จะใช้กับเมธอดที่ไม่มีการ return ข้อมูลและไม่มี Argument ใดๆ
ส่วน ParameterizedThreadSTart จะเป็นการส่งข้อมูลไปที่ Thread
หลังจากที่สร้าง Thread แล้วเราสามารถใช้เมธอด Start() ผ่านobject ของคลาส Thread เพื่อให้ Thread เริ่มทำงาน
ตัวอย่างการสร้าง Thread

Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        Thread t = new Thread(MyThread);
        t.Start();
        Console.ReadLine();
    }
    public static void MyThread()
    {
        Console.WriteLine("This is no parameter");
    }
}

การกำหนดรายละเอียดของ Thread เราสามารถกำหนดรายละเอียดต่างๆเกี่ยวกับ Thread ได้เช่น
Name = ชื่อThread
Priority = ลำดับความสำคัญของ Thread
ThreadState = สถานะของ Thread ได้แก่ Running,Aborted,Stopped,Suspended,Unstarted,WaitSleepJoin,StopRequested,AbortRequested,SuspendRequested,Background
IsAlive = ตรวจสอบสถานะว่ายังทำงานอยู่หรือไม่
IsBackground =ตรวจสอบว่าเป็น Background Thread หรือไม่
IsThreadPoolThread = ตรวจสอบว่าเป็น Thread pool หรือไม่

ตัวอย่างการแสดงรายละเอียดของ Thread

Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        Thread t = Thread.CurrentThread;
        Console.WriteLine(t.Priority);
        Console.WriteLine(t.IsAlive);
        Console.WriteLine(t.IsBackground);
        Console.ReadLine();
    }
   
}
ในตัวอย่างด้านบนนี้เป็นการแสดงรายละเอียดของ Threadปัจจุบัน(Main) สังเกตุได้ว่า ตรง IsAlive จะแสดง True เนื่องจากว่า หลังรันโปรแกรมแล้วทางโปรแกรมจะทำงาน
ที่เมธอด Main ซึ่งเมธอด Main กำลังทำงานอยู่จึงแสดงค่า True

การใช้ Anonymous เมธอด

Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        Thread t = new Thread(
            delegate()
            {
                Console.WriteLine("Thread is running....");
            });
        t.Start();
        Console.ReadLine();
    }
   
}

หรือ

Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        new Thread(
            delegate()
            {
                Console.WriteLine("Thread is running..");
            }).Start();
        Console.ReadLine();
    }
   
}
ผลลัพธ์จะเหมือนกับวิธีแรก



การส่งค่าไปที่ Thread โดยใช้ ParameterizedThreadStart


Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        Thread t = new Thread(ThreadWithParameter);
        t.Start("TestThreadWithParameter");
        Console.ReadLine();
    }
    public static void ThreadWithParameter(object o)
    {
        Console.WriteLine("Working in {0}",o);
    }
}
ในตัวอย่างนี้ ตรงที่เรียกใช้เมธอด Start จะมีการส่งค่า ไปที่เมธอด ThreadWithParameter ด้วย และใน เมธอดของ ThreadWithParameter จะมีการรับค่าเข้ามาแล้วแสดงค่า
ที่ส่งเข้ามา

การส่ง parameter โดยใช้ Struct

Code:
using System;
using System.Threading;
public struct SendingData
{
    public string data;
}
public class TestThreadWithSendingData
{
    public static void ThreadWithParameter(object o)
    {
        SendingData s = (SendingData)o;
        Console.WriteLine("'{0}' has been sent ", s.data);
    }
    public static void Main()
    {
        SendingData s = new SendingData();
        s.data = "one";
        Thread t = new Thread(ThreadWithParameter);
        t.Start(s);
        Console.ReadLine();
    }
}

การใช้สร้าง Thread โดยใช้ ThreadStart

Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        Thread t = new Thread(new ThreadStart(MyThread));
        t.Start();
        Console.ReadLine();
    }
    public static void MyThread()
    {
        Console.WriteLine("Hello");
    }
}

การใช้งาน เมธอด Sleep()เมธอด Sleep(int milliseconds) เป็นการกำหนดเวลาในการพักThread พอโปรแกรมทำงาน มาถึง เมธอด Sleep นี้ก็จะหยุดพักเป็นเวลาตาม ที่เรากำหนด ในเมธอดนี้พอถึงเวลา
แล้วก็จะทำงานต่อไป ในการเรียกใช้งานเมธอด Sleep ให้เรียกผ่านคลาส Thread ได้เลยดังนี้
Thread.Sleep(เวลา);

ตัวอย่างการใช้งาน Sleep

Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        Thread t = new Thread(new ParameterizedThreadStart(MyThread));
        t.Start(1000);
        Console.ReadLine();
    }
    public static void MyThread(object o)
    {
        int num = Convert.ToInt32(o);
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(i);
            Thread.Sleep(num);
        }
    }
}
ในตัวอย่างนี้จะเป็นการสร้าง Thread โดยใช้ ParameterizedThreadStart เพื่อส่งค่าเวลาที่มีค่าเท่ากับ 1 วินาที(1000 millissecond) ไปที่เมธอด MyThread ในเมธอดนี้รับค่าเข้ามา
เป็น object ดังนั้นจึงต้องแปลง object ให้เป็น int ก่อน และให้แสดงตัวเลขตั้งแต่ 0 ถึง 4 โดยพอแสดงตัวเลขตัวแรกก็จะหยุดพักเป็นเวลาที่รับเข้ามาซึ่งก็คือหยุดพัก 1 วินาที
แล้วค่อยแสดงตัวเลขถัดไปแล้วก็หยุดพักแล้วก็แสดงตัวเลขถัดไปเรื่อยๆถึงเลข 4

ตัวอย่าง Multiple Thread

Code:
using System;
using System.Threading;
class TestThread
{
    private int count;
    private Thread t;
    public TestThread(string str)
    {
        count = 0;
        t = new Thread(StartingThread);
        t.Name = str;
        t.Start();
    }
    public void StartingThread()
    {
       
        while (count < 5)
        {
            Console.WriteLine("..............{0} is starting................", t.Name);
            Console.WriteLine(count);
            Thread.Sleep(1000);
            count++;
        }
       
    }
}
public class TestMainThread
{
    public static void Main()
    {
        TestThread t1 = new TestThread("Threadone");
        TestThread t2 = new TestThread("Threadtwo");
        TestThread t3 = new TestThread("Threadthree");
        Console.ReadLine();
    }
}
ในตัวอย่างนี้ เป็นการทำงานแบบ multiple thread คือ สร้าง object ของคลาส TestThread 3 ตัวแล้วมีการส่งชื่อ Thread ทั้ง 3 ไปที่คลาสของ TestThread ในคอนสตรักเตอร์
ของคลาสนี้จะมีการเรียกใช้งาน Thread เพื่อไปเรียกเมธอด StartingThread ให้ทำงาน


การใช้งาน Background Threadในการสร้าง Thread นั้น Thread ที่ถูกสร้างขึ้นมาจะเป็น Foreground Thread ซึ่ง application ต่างๆที่ทำงานได้ก็ต่อเมื่อต้องมี Foreground Thread อย่างน้อยหนึ่งตัวถ้าไม่มี Foreground Thread แล้ว
application ก็จะหยุดทำงาน ดังนั้นถ้าใน application หนึ่งๆมี Foreground Thread มากกว่า 1 ตัวที่ทำงานอยู่ และ Main Thread หยุดทำงานแล้ว application ก็จะทำงานไปเรื่อยๆแล้วจะหยุดทำงานลงเมื่อ Foreground Thread ทั้งหมดทำงาน
เสร็จเรียบร้อย ดังนั้นถ้าเราต้องการเปลื่ยนจาก Foreground Thread ให้เป็น Background Thread ก็ใช้ properties ที่ชื่อว่า IsBackground
ตัวอย่าง

Code:
using System;
using System.Threading;
public class TestBackgroundThread
{
    public static void Main()
    {
        Thread t = new Thread(MyThread);
        t.Name = "one";//กำหนดชื่อ Thread ปัจจุบัน
        t.Start();
        Console.WriteLine("This Main Thread Stopped");
        Console.ReadLine();
    }
    public static void MyThread()
    {
        Console.WriteLine(" '{0}' started ", Thread.CurrentThread.Name);
        Console.WriteLine(" '{0}' finished", Thread.CurrentThread.Name);
    }
}
ในตัวอย่างด้านบนนี้ ผลลัพธ์จะแสดง
This Main Thread Stopped
'one' started
'one' finished
คำอธิบายเนื่องจากตัวอย่างนี้ มี MainThread และ Foreground Thread(ค่า Default เมื่อสร้างThread ใหม่) สังเกตุได้ว่าเมื่อ Main Thread หยุดทำงานแล้ว
(แสดง คำว่า This Main Thread Stopped ) foreground Thread ยังทำงานต่อได้โดยแสดงคำว่า 'one' started และ 'one' finished พอแสดงถึง one finished แล้ว
foreground Thread ก็จะหยุดทำงาน แต่ถ้าเราต้องการเปลื่ยนจาก ForeGround Thread เป็น Background ก็ใช้คำสั่ง t.IsBackground=true; ดังนี้

Code:
using System;
using System.Threading;
public class TestBackgroundThread
{
    public static void Main()
    {
        Thread t = new Thread(MyThread);
        t.Name = "one";//กำหนดชื่อ Thread ปัจจุบัน
        t.IsBackground = true;
        t.Start();
        Console.WriteLine("This Main Thread Stopped");
        Console.ReadLine();
    }
    public static void MyThread()
    {
        Console.WriteLine(" '{0}' started ", Thread.CurrentThread.Name);
        Thread.Sleep(1000);
        Console.WriteLine(" '{0}' finished", Thread.CurrentThread.Name);
    }
}

ผลลัพธ์ที่ได้จะขึ้นอยู่กับระบบของแต่ละเครื่องที่รันคำสั่งนี้ซึ่งจะให้ผลลัพธ์ไม่เหมือนกันบางเครื่องจะให้แสดง This Main Thread Stopped 'one' started หรืออาจจะแสดง
ทั้งหมดเหมือนกับ foreground เลยก็ได้

การใช้ Priority
Priority ใช้กำหนดระดับความสำคัญของ Thread โดย Thread ไหนที่มีความสำคัญกว่าก็จะแสดงขึ้นมาก่อน วิธีการกำหนด Priority มีดังนี้
t.Priority =ThreadPriority.Highest;
ชนิดของ Priority ต่างๆมีดังนี้
AboveNormal
BelowNormal
Normal
Highest
Lowest
โดยค่าเริ่มต้นถ้าเราไม่กำหนด Priority ก็จะเป็น Normal
ตัวอย่าง การใช้งาน Priority

Code:
using System;
using System.Threading;
public class TestBackgroundThread
{
    public static void Main()
    {
        Thread t1 = new Thread(MyThread);
        t1.Name = "one";
        t1.Priority = ThreadPriority.Lowest;
        Thread t2 = new Thread(MyThread);
        t2.Name = "two";
        t2.Priority = ThreadPriority.Highest;
        Thread t3 = new Thread(MyThread);
        t3.Name = "three";
        t3.Priority = ThreadPriority.Normal;
        t1.Start();//หลังจากเรียก Start() แล้วทางโปรแกรมจะแสดง ข้อความเป็นอันดับสุดท้ายเนื่องจากกำหนด Priority เป็น Lowest
        t2.Start();//หลังจากเรียก Start() แล้วทางโปรแกรมจะแสดง ข้อความเป็นอันดับแรกเนื่องจากกำหนด Priority เป็น Highest
        t3.Start();//หลังจากเรียก Start() แล้วทางโปรแกรมจะแสดง ข้อความเป็นอันดับตรงกลางเนื่องจากกำหนด Priority เป็น Normal
        Console.ReadLine();
    }
    public static void MyThread()
    {
        Console.WriteLine("Hello   "+Thread.CurrentThread.Name);
       
    }
}
ผลลัพธ์
Hello two
Hello three
Hello one

การใช้งาน Joinเมธอด Join() เป็นการบอกว่าให้ Thread ที่เรียกใช้งานเมธอดนี้หยุดรอก่อนแล้วเข้าสู่สถานะ WaitSleepJoin แล้วให้ Thread อื่นทำงานให้เสร็จก่อนแล้วถึงทำงานต่อ

Code:
using System;
using System.Threading;
class MyThread
{
    private string name;
    public Thread t;
    public MyThread(string name)
    {
        this.name = name;
         t = new Thread(TestJoin);
        t.Name = name;
        t.Start();
    }
    public void TestJoin()
    {
        for (int i = 0; i < 15; i++)
        {
     Console.WriteLine("Thread name is  {0} , {1}", Thread.CurrentThread.Name, i);
        }
    }
}
public class TestJoinThread
{
    public static void Main()
    {
        MyThread t1 = new MyThread("one");
        MyThread t2 = new MyThread("two");
        t1.t.Join();
        Console.WriteLine("t1 joined");
        t2.t.Join();
        Console.WriteLine("t2 joined");
        Console.ReadLine();
    }
}
ตัวอย่าง

Code:
using System;
using System.Threading;
public class TestThread
{
    public static void Main()
    {
        Thread t = new Thread(StartingThread);
        Console.WriteLine("{0} : StartingThread ", DateTime.Now.ToString("HH:mm:ss"));
        t.Start();
   
        if (!t.Join(2000))
        {
            Console.WriteLine("Joined Timeout at {0}", DateTime.Now.ToString("HH:mm:ss"));
        }
        t.Join();
        Console.ReadLine();
    }
    public static void StartingThread()
    {
        for (int i = 1; i < 10; i++)
        {
            Console.WriteLine(DateTime.Now.ToString("HH:mm:ss"));
            Thread.Sleep(1200);
        }
    }
}

Synchronizationในกรณีที่เราต้องการใช้ข้อมูลระหว่าง Thread ในกรณีที่มี Thread ตั้งแต่ สอง Thread ขึ้นไป เราจะต้องใช้ Synchronization เพื่อที่จะให้ Thread บางเธรดในช่วงเวลานั้นสามารถใช้ข้อมูลและเปลื่ยนสถานะได้โดยไม่เกี่ยวข้องกับThread อื่น
สิ่งที่เกี่ยวกับกับ Synchronization ได้แก่
Monitor,lock,Interlocked สามารถทำ Synchronization ได้ภายใน process นั้นๆ
Events,Mutex,Semaphore ใช้ทำ Synchronization ระหว่าง Thread หลายๆ process

ตัวอย่าง

Code:
using System;
using System.Threading;
class StateClass
{
    private int state = 1;
    public int State
    {
        get
        {
            return state;
        }
        set
        {
            state = value;
        }
    }
}
class Work{
     StateClass sc;
     public Work(StateClass sc)
    {
        this.sc = sc;
    }
    public void LoopMethod()
    {
        for (int i = 0; i < 10000; i++)
        {
           sc.State += 1;
        }
    }
}
public class TestThread
{
    public static void Main()
    {
        int n = 10;
        StateClass s = new StateClass();
        Thread[] t = new Thread[n];//สร้าง Array ของ Thread มีทั้งหมด 10 Thread
        for (int i = 0; i < n; i++)
        {
            t[i] = new Thread(new Work(s).LoopMethod);//สร้าง Thread โดยเรียกใช้งาน LoopMethod ของคลาส Work
            t[i].Start();
        }
        for (int i = 0; i < n; i++)
        {
            t[i].Join();//เรียกใช้ Join() เพื่อให้เธรดอื่นๆทำงาน
        }
        Console.WriteLine(s.State);
        Console.ReadLine();
    }
}

จากนั้นให้รันหลายๆครั้งสังเกตุว่าผลลัพธ์จะได้ตัวเลขต่างๆออกมาซึ่งแต่ละครั้งไม่เท่ากันเช่นเช่น
87238,90001,ฯซึ่งตัวอย่างนี้ยังไม่ได้ทำ Synchronize ดังนั้นเพื่อให้ค่าที่ออกมามีค่าที่เท่ากันทุกครั้งที่รันเราจะต้องใช้ Synchronize โดย
เราจะใช้ lock เพื่อ ล็อค สถานะเพื่อให้แสดงค่าเท่ากันรูปแบบ ของ lock มีดังนี้
lock(obj){
//คำสั่ง
}
ให้ใส่ lock ตรงสถานะทีเปลื่ยนแปลง ดังนี้
Code:
public void LoopMethod()
    {
        for (int i = 0; i < 10000; i++)
        {
            lock (sc)
            {
                sc.State += 1;
            }
        }
    }
แล้วทดลองรันดูผลลัพธ์จะได้ค่าที่เท่ากันในแต่ละครั้งออกมาเนื่องจากว่าเราได้ทำ Synchronize แล้วทำให้ช่วงเวลาหนึ่งๆ เธรดบางเธรดจะเข้าถึงข้อมูลได้เท่านั้น

อีกตัวอย่างเกี่ยวกับ Synchronize

Code:
using System;
using System.Threading;

class TestThread
{
  private int n = 0;

  public void Starts()
  {
        while (n < 5)
          {
              int tmp = n;
              tmp++;
              Console.WriteLine("Thread name is " + Thread.CurrentThread.Name + " " + tmp);
              Thread.Sleep(1000);
              n = tmp;
          }
}
  public static void Main()
  {
      TestThread t = new TestThread();
      Thread t1 = new Thread(new ThreadStart(t.Starts));
      Thread t2 = new Thread(new ThreadStart(t.Starts));
      Thread t3 = new Thread(new ThreadStart(t.Starts));
      t1.Name = "one";
      t2.Name = "two";
      t3.Name = "three";
      t1.Start();
      t2.Start();
      t3.Start();
      Console.ReadLine();
  }
}




ผลลัพธ์จะได้
Thread name is one 1
Thread name is two 1
Thread name is three 1
Thread name is one 2
Thread name is two 2
Thread name is three 2
... ไปเรื่อยๆ ถึง 5
ในตัวอย่างนี้ยังไม่ได้ทำการ Synchronize ดังนั้นสังเกตผลลัพธ์ว่า มี 3 Thread ด้วยกัน คือ (one,two,three) ในการเข้าถึงข้อมูลเดียวกันพร้อมกัน คือเธรด one,two,three สามารถเข้าถึงข้อมูล เลข 1 ได้พร้อมกันในเวลาเดียวกัน
ดังนั้นถ้าต้องการให้มีเธรดเดียวเข้าถึงข้อมูลได้ในเวลานั้นๆก็ใช้ lock ดังนี้
ในเมธอด Starts() ให้ใส่ lock เข้าไปดังนี้
Code:
public void Starts()
  {
      lock (this)
      {
          while (n < 5)
          {
              int tmp = n;
              tmp++;
              Console.WriteLine("Thread name is " + Thread.CurrentThread.Name + " " + tmp);
              Thread.Sleep(1000);
              n = tmp;
          }
      }
}
ผลลัพธ์จะได้
Thread name is one 1
Thread name is one 2
Thread name is one 3
Thread name is one 4
Thread name is one 5
สังเกตว่าหลังจากใส่ lock แล้วจะทำให้มีแค่เพียงเธรดเดียวในการเข้าถึงข้อมูล

การใช้งาน Interlocked
ปกติแล้วการเพิ่มค่าอย่างเช่น i++ จะไม่ใช้ Thread-Safe เนื่องจากว่า i++ เป็นการเพิ่มค่าที่มาจากหน่วยความจำ(memory) คือเพิ่มค่าที่ละหนึ่งเข้าไปแล้วเก็บค่าไว้ใน
หน่วยความจำซึ่งกระบวนการนี้ทำให้เกิดการขัดจังหวะหรือ interrupted โดย thread scheduler ได้ดังนั้นในการเพิ่มค่าหรือลดค่าต่างๆจะใช้ Interlocked เพื่อป้องกันไม่
ให้เกิดปัญหาในกรณีที่จัดการเกี่ยวกับ Thread
เมธอดต่างๆได้แก่
Add() , CompareExchange(),Decrement(),Exchange(),Increment(),Read()
ตัวอย่าง การใช้งาน Interlocked

Code:
using System;
using System.Threading;
public class TestThread
{
    private int n = 0;
    public void Starts()
    {
        while (n < 5)
        {
            Interlocked.Increment(ref n);
            Console.WriteLine("Thread name is " + Thread.CurrentThread.Name + "   " + n);
            Thread.Sleep(1000);
        }
    }
    public static void Main()
    {
        TestThread t =new TestThread();
        Thread t1 = new Thread(new ThreadStart(t.Starts));
        Thread t2 = new Thread(new ThreadStart(t.Starts));
        Thread t3 = new Thread(new ThreadStart(t.Starts));
        t1.Name = "one";
        t2.Name = "two";
        t3.Name = "three";
        t1.Start();
        t2.Start();
        t3.Start();
        Console.ReadLine();
    }
}
ผลลัพธ์จะคล้ายๆกับ การใช้งาน lock แต่ต่างกันที่ว่า ในตัวอย่างก่อนหน้านี้ใช้ tmp++ เพื่อเพิ่มค่าแล้วเก็บในหน่วยความจำแต่ในตัวอย่างนี้จะใช้
Interlocked.Increment(ref n); เพิ่อเพิ่มค่า ผลลัพธ์จะได้(แต่ละเครื่องอาจจะไม่เหมือนกัน)
Thread name is one 1
Thread name is two 2
Thread name is three 3
Thread name is one 4
Thread name is two 5

การใช้งาน Monitor

Monitor จะคล้ายๆกับ lock แต่สามารถเพิ่ม ค่าของเวลาในการ lock ได้ รูปแบบ
Monitor.Enter(obj,<time>);
try{
//คำสั่ง
}
finally{
Monitor.Exit(obj);
}
ตัวอย่างการใช้งาน Monitor

Code:
using System;
using System.Threading;
public class TestThread
{
    private int n = 0;
    public void Starts()
    {
        Monitor.Enter(this);
        try
        {
            while (n < 5)
            {
                Interlocked.Increment(ref n);
                Console.WriteLine("Thread name is " + Thread.CurrentThread.Name + "   " + n);
                Thread.Sleep(1000);
            }
        }
        finally
        {
            Monitor.Exit(this);
        }
    }
    public static void Main()
    {
        TestThread t =new TestThread();
        Thread t1 = new Thread(new ThreadStart(t.Starts));
        Thread t2 = new Thread(new ThreadStart(t.Starts));
        Thread t3 = new Thread(new ThreadStart(t.Starts));
        t1.Name = "one";
        t2.Name = "two";
        t3.Name = "three";
        t1.Start();
        t2.Start();
        t3.Start();
        Console.ReadLine();
    }
}
ผลลัพธ์จะได้
Thread name is one 1
Thread name is one 2
Thread name is one 3
Thread name is one 4
Thread name is one 5

ไม่มีความคิดเห็น: