Thứ Hai, ngày 25 tháng 1 năm 2010

// // Leave a Comment

C# Threading

Bài này quá hay về thread trong C#, tôi rất muốn dịch sang tiếng việt cho dễ coi nhưng thật sự không biết dùng từ như thế nào cho đúng và dễ hiểu. Mọi người chịu khó đọc bằng tiếng anh cho nó nguyên nghĩa.

1. Simple multithreaded example in c#.
The method passed as the ThreadStart delegate can have no arguments and must return void.
using System;
using System.Threading;

public class ThreadTest
{
    public class MyJob
    {
        public int repetitions = 1;
        public void runme()
        {
            for (int i = 0; i < repetitions; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine(Thread.CurrentThread.Name);
            }
        }
    }

    public static void Main(string[] args)
    {
        MyJob myJob = new MyJob();
        myJob.repetitions = 3;
        Thread thread = new Thread(new ThreadStart(myJob.runme));
        thread.Name = "first";
        thread.Start();

        MyJob myJob2 = new MyJob();
        myJob2.repetitions = 5;
        Thread thread2 = new Thread(new ThreadStart(myJob2.runme));
        thread2.Name = "second";
        thread2.Start();
    }
}
This produces
first
second
first
second
first
second
second
second
2. Concurrency
When two threads change the same data, bad things(tm) can happen. In the following example two threads increment and then decrement a global variable. In C# incrementing and decrementing are not necessarily atomic.
using System;
using System.Threading;

public class ThreadTest2
{
    public class MyJob
    {
        public static int counter = 0;
        public int repetitions = 100000000;
        public void runme()
        {
            for (int i = 0; i < repetitions; i++)
            {
                counter++;
                counter--;
            }
            Console.WriteLine(Thread.CurrentThread.Name + ": " + counter);

        }
    }

    public static void Main(string[] args)
    {
        MyJob myJob = new MyJob();
        Thread thread = new Thread(new ThreadStart(myJob.runme));
        thread.Name = "first";

        MyJob myJob2 = new MyJob();
        Thread thread2 = new Thread(new ThreadStart(myJob2.runme));
        thread2.Name = "second";

        thread.Start();
        thread2.Start();
    }
}
The results can be:
first: 0
second: 0
or sometimes
first: 1
second: 0
We can never be sure of the outcome. We can overcome this in several ways.

3. Using Threading.Interlocked methods.
Using Threading.Interlocked.Increment the CLR will guarentee an atomic operation. Another interesting method in the namespace is "Exchange()" which swaps two values atomically.
public class MyJob
{
    public static int counter = 0;
    public int repetitions = 100000000;
    public void runme()
    {
        for (int i = 0; i < repetitions; i++)
        {
            System.Threading.Interlocked.Increment(ref i);
            System.Threading.Interlocked.Decrement(ref i);
        }
        Console.WriteLine(Thread.CurrentThread.Name + ": " + counter);

    }
}
The results will be:
first: 0
second: 0
4. Using "lock()" to synchronize lock() takes an object as the argument.
In this example we can use "this" since both threads are using the same "MyJob" instance. In static functions you can use the "type" object, e.g., "lock(typeof(Util))" to synchronize.
public class MyJob
{
    public static int counter = 0;
    public int repetitions = 100000000;
    public void runme()
    {
        lock (this)
        {
            for (int i = 0; i < repetitions; i++)
            {
                counter++;
                counter--;
            }
        }
        Console.WriteLine(Thread.CurrentThread.Name + ": " + counter);

    }
}
5. The Synchronization attribute can be used.
This locks the entire object - everything is single access. Note the object must descend from ContextBoundObject.
[System.Runtime.Remoting.Contexts.Synchronization]
public class MyJob : ContextBoundObject
{
    public static int counter = 0;
    public int repetitions = 100000000;
    public void runme()
    {
        for (int i = 0; i < repetitions; i++)
        {
            counter++;
            counter--;
        }
        Console.WriteLine(Thread.CurrentThread.Name + ": " + counter);

    }
}
6. Example of using Join()
Join() hitches the fate of a thread to the current thread. Execution of the calling thread will wait until the callee's process completes. In this example we Join on a thread that takes 2 seconds to complete, then Join on a second that still has 2 seconds to go.
using System;
using System.Threading;
//Example showing use of Join()
public class ThreadTest4
{
    public class MyJob
    {
        public static int counter = 0;
        public int waitfor = 1;
        public int finalState = -1;
        //sleeps then fills in finalState to simulate work done
        public void runme()
        {
            Thread.Sleep(waitfor);
            finalState = waitfor;
            Console.WriteLine(Thread.CurrentThread.Name + " finished sleeping finalState: " + finalState);
        }
    }

    public static void Main(string[] args)
    {
        MyJob myJob = new MyJob();
        myJob.waitfor = 2000;
        Thread thread = new Thread(new ThreadStart(myJob.runme));
        thread.Name = "first";

        MyJob myJob2 = new MyJob();
        myJob2.waitfor = 4000;
        Thread thread2 = new Thread(new ThreadStart(myJob2.runme));
        thread2.Name = "second";

        thread.Start();
        thread2.Start();

        Console.WriteLine("After start.");
        Console.WriteLine("Before first join.");
        thread.Join();
        Console.WriteLine("After first join.");
        Console.WriteLine("Before second join.");
        thread2.Join();
        Console.WriteLine("After second join.");

        Console.WriteLine("myJob.finalState=" + myJob.finalState);
        Console.WriteLine("myJob2.finalState=" + myJob2.finalState);
    }
}
This produces
After start.
Before first join.
first finished sleeping finalState: 2000
After first join.
Before second join.
second finished sleeping finalState: 4000
After second join.
myJob.finalState=2000
myJob2.finalState=4000
7. Join() with timeout 
What if a process may never finish? The Join() method has an optional parameter specifying how many millisecs to wait. The Join will give up after that time and return a 'false'. The following example shows the main thread waiting 2 seconds for a job that will take 8 seconds. After waiting 2 seconds, it gets a 'false' value back implying it did not finish in 2 seconds. Lacking any mercy we waste the process and move on to wait for the second, which has already completed.
using System;
using System.Threading;
//Example showing use of Join()
public class ThreadTest6
{
    public class MyJob
    {
        public static int counter = 0;
        public int waitfor = 1;
        public int finalState = -1;
        //sleeps then fills in finalState to simulate work done
        public void runme()
        {
            Thread.Sleep(waitfor);
            finalState = waitfor;
            Console.WriteLine(Thread.CurrentThread.Name + " finished sleeping finalState: " + finalState);
        }
    }

    public static void Main(string[] args)
    {
        MyJob myJob = new MyJob();
        myJob.waitfor = 8000;
        Thread thread = new Thread(new ThreadStart(myJob.runme));
        thread.Name = "first";

        MyJob myJob2 = new MyJob();
        myJob2.waitfor = 500;
        Thread thread2 = new Thread(new ThreadStart(myJob2.runme));
        thread2.Name = "second";

        thread.Start();
        thread2.Start();

        Console.WriteLine("After start.");
        Console.WriteLine("Before first join.");
        bool finished = thread.Join(2000);
        if (!finished) { thread.Abort(); }
        Console.WriteLine("finishedP:" + finished);

        Console.WriteLine("After first join.");
        Console.WriteLine("Before second join.");
        thread2.Join(2000);
        Console.WriteLine("After second join.");

        Console.WriteLine("myJob.finalState=" + myJob.finalState);
        Console.WriteLine("myJob2.finalState=" + myJob2.finalState);
    }
}
Which produces:
After start.
Before first join.
second finished sleeping finalState: 500
finishedP:False
After first join.
Before second join.
After second join.
myJob.finalState=-1
myJob2.finalState=500

0 comments: