Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Threads example

Discussion in 'Scripting' started by _ivansc, May 11, 2012.

  1. _ivansc

    _ivansc

    Joined:
    Mar 27, 2012
    Posts:
    14
    Hey guys..
    I'm studying threads on college (with c++) an someone told me that C# have thread system by default (C++ does not have it, you will need an extra lib to do that), and I had the necessity of learn how :D.

    For those who don't know what a thread is:
    -Thread is a function that you can call paralleled with the main Unity's thread. You can do, for an example, a infinity loop like "while(true){}" in a thread without crashing you app, because its being executed in another "process" than game's one.

    Well, thread is an advanced programming feature, and requires a bit of time to learn what it is, and what you can do with this...

    For those looking for an "how to" for using threads on unity or even in another C# apps:
    Code (csharp):
    1.  
    2. /*
    3.  *This script just creats 2 threads that draw its number under a GUI label.
    4.  * First time the button is used its call the start() of its respective thread.
    5.  * Second time the button is used it pause/play the Thread.
    6.  * To make it work, you just need to add this script to any object in the scene!
    7.  *
    8.  * Made by Ivan S. Cavalheiro
    9.  * Date: 10/05/2012
    10.  * Mail: ivanscavalheiro@hotmail.com
    11.  * Script vers.: 1.0
    12. */
    13.  
    14. using UnityEngine;
    15. using System.Collections;
    16. using System.Threading;
    17.  
    18. public class MainThreads : MonoBehaviour
    19. {
    20.     #region Public data
    21.     public float timeWaiting = 5000000.0f;
    22.     public string labelInitialText = "I`m the console here!";
    23.     #endregion
    24.    
    25.     #region Private data
    26.     private string _label;
    27.     private Thread _t1;
    28.     private Thread _t2;
    29.     private bool _t1Paused = false;
    30.     private bool _t2Paused = false;
    31.     #endregion
    32.    
    33.     #region Start
    34.     void Start () {
    35.         _label = labelInitialText;
    36.         _t1 = new Thread(_func1);
    37.         _t2 = new Thread(_func2);
    38.     }
    39.     #endregion
    40.    
    41.     #region Threads
    42.     private void _func1()
    43.     {
    44.         if(_label == labelInitialText)
    45.             _label = "";
    46.         while(true)
    47.         {
    48.             _label += 1;
    49.             for(int i = 0; i < timeWaiting; i ++)
    50.                 while(_t1Paused){}
    51.         }
    52.     }
    53.    
    54.     private void _func2()
    55.     {
    56.         if(_label == labelInitialText)
    57.             _label = "";
    58.         while(true)
    59.         {
    60.             _label += 2;
    61.             for(int i = 0; i < timeWaiting; i ++)
    62.                 while(_t2Paused){}
    63.         }
    64.     }
    65.     #endregion
    66.    
    67.     #region OnGUI
    68.     void OnGUI()
    69.     {
    70.         //--> Label that servers as a "console"
    71.         GUI.Label(new Rect(0,0, 500, 500), _label);
    72.        
    73.         //--> Button for thread 1
    74.         if(GUI.Button(new Rect(50, 50, 100, 50), "Thread T1"))
    75.         {
    76.             if(!_t1.IsAlive)
    77.                 _t1.Start();
    78.             else
    79.                 _t1Paused = !_t1Paused;
    80.            
    81.         }
    82.        
    83.         //--> Button for thread 2
    84.         if(GUI.Button(new Rect(50, 120, 100, 50), "Thread T2"))
    85.         {
    86.             if(!_t2.IsAlive)
    87.                 _t2.Start();
    88.             else
    89.                 _t2Paused = !_t2Paused;
    90.         }
    91.     }
    92.     #endregion
    93. }
    94.  
    95.  
     
  2. Munei

    Munei

    Joined:
    Apr 28, 2012
    Posts:
    20
    Muito obrigrado, Threading is a complicated subject, i heard that when you have to sync your threads is when the nightmares comes, please if you keep learning in this subject keep posting, when i studied we learned C without the ++ so i'm missing out a lot of fun and usefull stuff :)
     
  3. rhasami

    rhasami

    Joined:
    Apr 30, 2012
    Posts:
    72
    I found this: " the Unity API isn't thread-safe and can't be used in threads."
    and this:
    "You can use your own threads for calculating something in the background or communicate with a native code plugin but not to interact with Unity."

    here: http://answers.unity3d.com/questions/180243/threading-in-unity.html

    Maybe coroutines are the better way for doing parallel things if you access the unity api.
     
    Last edited: May 11, 2012
  4. _ivansc

    _ivansc

    Joined:
    Mar 27, 2012
    Posts:
    14
    @rhasami
    Yeah, i have no time to make more tests now, but i was able to change shared variables with threads..
    this means that you can make classes with load functions and use this functions inside a thread, so your game wont have a kind of a freeze every time you want to load something...

    of course you will need a good algorithm and a good programming logic to avoid errors like showing something that isnt loaded yet...

    but again, this is a advanced programming feature.

    @munei
    i'll. Syncing isn't that hard! If you must wait some response from thread before making anything in the main thread just call the .wait() function, that will "stop" your program till that thread finish all it's job
     
  5. rhasami

    rhasami

    Joined:
    Apr 30, 2012
    Posts:
    72
    Its very good to have advanced professionals like you in the community.

    Thank you very much
     
  6. Sirex

    Sirex

    Joined:
    Feb 7, 2011
    Posts:
    77
    That code is not safe. You are not locking the variable _label !
    Trust me, i just in the week passed a semester long labb series on operating system and process management where the labb was to implement process and file and thread managemnt to a school operting system in c, http://en.wikipedia.org/wiki/Pintos .

    Actually what you have posted is a classic example of when you get sync errors!
    I will explain: Take the example that _label=10 and we are in _func1.
    Now the line _label += 1; get translated to the psedo assembly code:

    move.l _label to temporaryRegister1;
    add.b 1 to temporaryRegister1;
    move.l temporaryRegister1 to _label;

    Lets say that we get an interrupt and a thread change directly after the add.l. (the example works after the first move.l also)
    _func1 threads temporaryRegister1 will be 11 now. Now then _func2 gets to run, 5 times. And it will manage to bumb up _label to 15.
    And we get interupt and thread switch back to _func1 thread. But thread1 temporaryRegister1 is still 11!!!
    Thus when we reach "move.l temporaryRegister1 to _label;" _func1 will write 11 to _label when _label is 15!
    Problem!
    We need to lock, wither with lock or semaphore, the variable _label everytime we write or read it so that we do not get this error.
     
    Last edited: May 11, 2012
    Roman200333 and 40detectives like this.
  7. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
    It will work fine as long as the two threads never run at the same time. :)
     
  8. Sirex

    Sirex

    Joined:
    Feb 7, 2011
    Posts:
    77
    Are you trolling me?
     
  9. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Just to clarify - Coroutines do not run in parallel.
     
    RiverExplorer likes this.
  10. rhasami

    rhasami

    Joined:
    Apr 30, 2012
    Posts:
    72
    Can you explain why not? I thought they do.

    Thanks
     
  11. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Coroutines are executed in the main thread where Update, FixedUpdate, and all other Unity's stuff runs. So if you execute Thread.Sleep(10000) in a coroutine then the whole application will hang for 10 seconds.

    --
    Here is my version of the example. It doesn't waste CPU time, terminates correctly, and, I hope, is thread safe (if not, please, let me know.)

    Code (csharp):
    1. using System.Threading;
    2. using UnityEngine;
    3.  
    4. public class MainThreads : MonoBehaviour
    5. {
    6.     public string labelInitialText = "I'm the console here!";
    7.  
    8.     private string label;
    9.     private volatile bool workInProgress = false;
    10.     private Thread t1;
    11.     private Thread t2;
    12.  
    13.     private volatile bool t1PausedFlag = false;
    14.     private volatile bool t2PausedFlag = false;
    15.     private volatile bool cancelFlag = false;
    16.  
    17.     private void Start()
    18.     {
    19.         label = labelInitialText;
    20.  
    21.         t1 = new Thread(Func1) {Name = "Thread 1"};
    22.         t2 = new Thread(Func2) {Name = "Thread 2"};
    23.     }
    24.  
    25.     private void Func1()
    26.     {
    27.         lock (label)
    28.         {
    29.             if (workInProgress == false)
    30.             {
    31.                 workInProgress = true;
    32.                 label = "";
    33.             }
    34.         }
    35.  
    36.         while (cancelFlag == false)
    37.         {
    38.             lock (label)
    39.             {
    40.                 label += "1";
    41.             }
    42.  
    43.             do
    44.             {
    45.                 Thread.Sleep(200);
    46.             } while (t1PausedFlag);
    47.         }
    48.  
    49.         Debug.Log(string.Format("{0} finished", Thread.CurrentThread.Name));
    50.     }
    51.  
    52.     private void Func2()
    53.     {
    54.         lock (label)
    55.         {
    56.             if (workInProgress == false)
    57.             {
    58.                 workInProgress = true;
    59.                 label = "";
    60.             }
    61.         }
    62.  
    63.         while (cancelFlag == false)
    64.         {
    65.             lock (label)
    66.             {
    67.                 label += "2";
    68.             }
    69.  
    70.             do
    71.             {
    72.                 Thread.Sleep(200);
    73.             } while (t2PausedFlag);
    74.         }
    75.  
    76.         Debug.Log(string.Format("{0} finished", Thread.CurrentThread.Name));
    77.     }
    78.  
    79.     private void OnGUI()
    80.     {
    81.         //--> Label that servers as a "console"
    82.         GUI.Label(new Rect(0, 0, 500, 500), label);
    83.  
    84.         //--> Button for thread 1
    85.         if (GUI.Button(new Rect(50, 50, 100, 50), "Thread T1"))
    86.         {
    87.             if (!t1.IsAlive)
    88.             {
    89.                 t1.Start();
    90.             }
    91.             else
    92.             {
    93.                 t1PausedFlag = !t1PausedFlag;
    94.             }
    95.         }
    96.  
    97.         //--> Button for thread 2
    98.         if (GUI.Button(new Rect(50, 120, 100, 50), "Thread T2"))
    99.         {
    100.             if (!t2.IsAlive)
    101.             {
    102.                 t2.Start();
    103.             }
    104.             else
    105.             {
    106.                 t2PausedFlag = !t2PausedFlag;
    107.             }
    108.         }
    109.     }
    110.  
    111.     private void OnApplicationQuit()
    112.     {
    113.         cancelFlag = true;
    114.     }
    115. }
     
    Last edited: May 11, 2012
    Marcos-Elias and Ali-Nagori like this.
  12. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    This is a very good article to start with - http://www.albahari.com/threading/
     
  13. Tseng

    Tseng

    Joined:
    Nov 29, 2010
    Posts:
    1,217
    A co-routine runs in a main thread, as alex said. A coroutine like

    Code (csharp):
    1.  
    2. void Start() {
    3.     StartCoroutine(ExampleCoroutine);
    4. }
    5. public IEnumerator ExampleCoroutine() {
    6.     while(true) {
    7.         yield return null;
    8.     }
    9. }
    10.  
    Would basically run every frame (similar to Update). The advantage is that you can spread calculations over several frames, for example moving the target to a certain position or do some expensive calculations (like AI, looking for close targets) over several frames so you don't get short "lockups" (where the game freezes for 0.1 second)
     
    imDanOush likes this.
  14. _ivansc

    _ivansc

    Joined:
    Mar 27, 2012
    Posts:
    14
    OK, here is the final solution for sync problems: mutex.

    Mutex is a method that locks itself until it unlock itself. It can only be locked if its not already locked.
    What i do in this new version of the script is lock a mutex before changing the label content, change label content and than unlock it.
    If the another thread try to lock the mutex and its already locked, the thread will be forced to wait until its not locked anymore, so it can proceed.

    Code (csharp):
    1.  
    2. /*
    3.  *This script just creats 2 threads that draw its number under a GUI label.
    4.  * First time the button is used its call the start() of its respective thread.
    5.  * Second time the button is used it pause/play the Thread.
    6.  * To make it work, you just need to add this script to any object in the scene!
    7.  *
    8.  * Made by Ivan S. Cavalheiro
    9.  * Date: 10/05/2012
    10.  * Mail: ivanscavalheiro@hotmail.com
    11.  * Script vers.: 1.2
    12. */
    13.  
    14. using UnityEngine;
    15. using System.Collections;
    16. using System.Threading;
    17.  
    18. public class Threads : MonoBehaviour
    19. {
    20.     #region Public data
    21.     public float timeWaiting = 5000000.0f;
    22.     public string labelInitialText = "I`m the console here!";
    23.     #endregion  
    24.  
    25.     #region Private data
    26.     private string _label;
    27.     private Thread _t1;
    28.     private Thread _t2;
    29.     private bool _t1Paused = false;
    30.     private bool _t2Paused = false;
    31.     private Mutex _mutex = new Mutex();
    32.     #endregion
    33.  
    34.     #region Start
    35.     void Start ()
    36.     {
    37.         _label = labelInitialText;
    38.         _t1 = new Thread(_func1);
    39.         _t2 = new Thread(_func2);
    40.     }
    41.     #endregion
    42.  
    43.     #region Threads
    44.     private void _func1()
    45.     {
    46.         if(_label == labelInitialText)
    47.             _label = "";
    48.        
    49.         while(true)
    50.         {
    51.             _mutex.WaitOne();
    52.                 _label += 1;
    53.             _mutex.ReleaseMutex();
    54.            
    55.             for(int i = 0; i < timeWaiting; i ++)
    56.                 while(_t1Paused){}
    57.         }
    58.     }
    59.  
    60.     private void _func2()
    61.     {
    62.         if(_label == labelInitialText)
    63.             _label = "";
    64.  
    65.         while(true)
    66.         {
    67.             _mutex.WaitOne();
    68.                 _label += 2;
    69.             _mutex.ReleaseMutex();
    70.            
    71.             for(int i = 0; i < timeWaiting; i ++)
    72.                 while(_t2Paused){}
    73.         }
    74.     }
    75.     #endregion
    76.  
    77.     #region OnGUI
    78.     void OnGUI()
    79.     {
    80.         //--> Label that servers as a "console"
    81.         GUI.Label(new Rect(0,0, 500, 500), _label);
    82.  
    83.         //--> Button for thread 1
    84.         if(GUI.Button(new Rect(50, 50, 100, 50), "Thread T1"))
    85.         {
    86.             if(!_t1.IsAlive)
    87.                 _t1.Start();
    88.             else
    89.                 _t1Paused = !_t1Paused;
    90.         }
    91.  
    92.         //--> Button for thread 2
    93.         if(GUI.Button(new Rect(50, 120, 100, 50), "Thread T2"))
    94.         {
    95.             if(!_t2.IsAlive)
    96.                 _t2.Start();
    97.             else
    98.                 _t2Paused = !_t2Paused;
    99.         }
    100.     }
    101.     #endregion
    102. }
    103.  
     
    RiverExplorer likes this.
  15. rhasami

    rhasami

    Joined:
    Apr 30, 2012
    Posts:
    72
    Thanks Tseng, Alex for you explanation.

    Still don't understand 100%

    If I do...

    Code (csharp):
    1.  
    2. StartCoroutine("co1");
    3. StartCoroutine("co2");
    4. StartCoroutine("co3");
    5.  
    then all three Coroutines would run at the same time, not right? Still the same thread but kind of parallel? If this is not parallel what is this called? Semiparallel? Asynchron?

    I know its not real multithreading, it always runs in the same thread, same core.

    But if figured out for me its the best way to do things at the same time. Maybe not the fastest, then threads are better (just calculation, no unity api access), but only faster on multicore machines.

    This is how I understand threads and coroutines. Please correct me if I am wrong.

    Thanks a lot for explaining this.
     
    Last edited: May 12, 2012
  16. Tseng

    Tseng

    Joined:
    Nov 29, 2010
    Posts:
    1,217
    A coroutine is actually nothing other than an IEnumerable implementation (in C# you must declare the return type as such)

    Code (csharp):
    1.  
    2.     public IEnumerable ExampleCoroutine() {
    3.     }
    4.  
    the "original" idea behind IEnumerable was to implement collections for the use in "foreach" statement. The IEnumerable interface has only 3 members: 1 "Current" Property and 2 Methods called "Reset" and "GetNext". GetNext returns a boolean (true if there is a next object in the collection or false when there are no next objects)

    A foreach loop would keep calling "GetNext" in each foreach cycle until it has iterated through the whole collection and then stop.

    A good example of IEnumerator usage

    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4.  
    5. public class Program
    6. {
    7.     static void Main()
    8.     {
    9.     //
    10.     // Compute two with the exponent of 30.
    11.     //
    12.     foreach (int value in ComputePower(2, 30))
    13.     {
    14.         Console.Write(value);
    15.         Console.Write(" ");
    16.     }
    17.     Console.WriteLine();
    18.     }
    19.  
    20.     public static IEnumerable<int> ComputePower(int number, int exponent)
    21.     {
    22.     int exponentNum = 0;
    23.     int numberResult = 1;
    24.     //
    25.     // Continue loop until the exponent count is reached.
    26.     //
    27.     while (exponentNum < exponent)
    28.     {
    29.         //
    30.         // Multiply the result.
    31.         //
    32.         numberResult *= number;
    33.         exponentNum++;
    34.         //
    35.         // Return the result with yield.
    36.         //
    37.         yield return numberResult;
    38.     }
    39.     }
    40. }
    Source: http://www.dotnetperls.com/yield

    Unity uses the IEnumerators in a similar way. Basically every code "inbetween" the yield statements is like an object in a collection (array, list, dictionary etc.) which is executed on each step (GetNext() call).

    Also from the same site


    Basically Unity keeps a list of all active IEnumerators (Coroutines) and on each update loops through this list, calling "GetNext" and removes it when it reaches the end. The "IEnumerator.Current" Property returns the current YieldInstruction (i.e. WaitForSeconds, WaitForFixedUpdate, WaitForEndOfFrame).

    Something like this in pseudocode

    Code (csharp):
    1.  
    2. foreach coroutine in coroutineList
    3.     if coroutine.Current is null or coroutine.Current condition is true// i.e. time in seconds passed, end of frame reached etc.
    4.         if not coroutine.GetNext
    5.             remove coroutine from coroutineList // GetNext returned false, the coroutine finished
    6.  
     
  17. rhasami

    rhasami

    Joined:
    Apr 30, 2012
    Posts:
    72
    Thanks a lot Tseng, that really helped and makes it much clearer.
    StartCoroutine() just adds my function to the coroutine list which is handled by unity each frame.
     
  18. Tseng

    Tseng

    Joined:
    Nov 29, 2010
    Posts:
    1,217
    Well it's a very simplified explanation on it. It's probably implemented slightly differently, only Unity developers know.

    But that's the basic idea on how it works
     
  19. rhasami

    rhasami

    Joined:
    Apr 30, 2012
    Posts:
    72
    But its very good to understand the way you explained it.

    Just one last question if this is ok.

    I read somewhere (don't remember where) its better use coroutines instead of the update function.
    This would only make sense if I don't execute the code in the coroutine each frame by using WaitForSeconds() or so. Just "yield return"
    wouldn't be a performance advantage.
    Can you confirm this or is this a wrong conclusion?

    Thanks again
     
  20. Tseng

    Tseng

    Joined:
    Nov 29, 2010
    Posts:
    1,217
    Dunno, never did any performance tests on it.

    Though I use Coroutines only when I have to, because if you call "StopAllCoroutines" it will stop any. So if you do some important stuff (like movement) in coroutines it will be stopped to, which may cause some unexpected behavior
     
  21. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I didn't test it either, but it sounds reasonable. And also there is another way - InvokeRepeating(...), which is more simple and straightforward than coroutines.
     
  22. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Unity doesn't enforce the implementation of any particular Interface for any MonoBehaviour derivatives. If it did, it would probably enforce implementations of things like Awake, Start, Update, FixedUpdate. Because these are not mandatory, the assumption would be that the engine uses reflection to determine whether or not a MonoBehaviour has an Update method and if it does to invoke it on that particular frame. Not using Update and instead using a Coroutine with an infinite loop would save you that reflection check and invocation - therefore (theoretically) gaining you some measure of performance.

    However - I am in the same boat as Tseng and have never benchmarked it myself to prove any of the stuff I just said.
     
  23. zvidrih

    zvidrih

    Joined:
    Jan 31, 2017
    Posts:
    4
    Hi,

    although it's a quite old post I think it's still relevant. This post helped me a lot for I/O operations. The code suggested by allexzzzz works. If you want to use _ivansc code with _mutex you need to add another check in the inside loop in thread worker function:

    Code (CSharp):
    1. private void _func2()
    2.   {
    3.     if (_label == labelInitialText)
    4.       _label = "";
    5.  
    6.     while(!cancelFlag)
    7.     {
    8.       _mutex.WaitOne();
    9.       _label += 2;
    10.       _mutex.ReleaseMutex();
    11.  
    12.       for (int i = 0; i < timeWaiting; i++)
    13.         while (_t2Paused) {
    14.           if (cancelFlag)
    15.             break;
    16.         }
    17.     }
    18.   }
    And don't forget OnApplicationQuit. :)

    Thanks for great content!
     
    40detectives likes this.
  24. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Does Thread.Aboard() is not working instead of if (cancelFlag) break; in _ivansc code?

    Unity 2017.1 hangs after start, stop and start again and use only
    Code (CSharp):
    1. void OnApplicationQuit(){
    2.    _t1.Aboard();
    3.    _t2.Aboard();
    4. }
    5.  
     
  25. kudchikarsk

    kudchikarsk

    Joined:
    Jul 30, 2018
    Posts:
    2
    Threads in C# are modelled by Thread Class. When a process starts (you run a program) you get a single thread (also known as the main thread) to run your application code. To explicitly start another thread (other than your application main thread) you have to create an instance of thread class and call its Start method to run the thread using C#, Let’s see an example

    Code (CSharp):
    1.  using System;
    2. using System.Diagnostics;
    3. using System.Threading;
    4.  
    5. public class Example
    6. {
    7.      public static void Main()
    8.      {
    9.          //initialize a thread class object
    10.          //And pass your custom method name to the constructor parameter
    11.          Thread t = new Thread(SomeMethod);
    12.  
    13.          //start running your thread
    14.          t.Start();
    15.  
    16.          //while thread is running in parallel
    17.          //you can carry out other operations here      
    18.  
    19.          Console.WriteLine("Press Enter to terminate!");
    20.          Console.ReadLine();
    21.      }
    22.  
    23.      private static void SomeMethod()
    24.      {
    25.          //your code here that you want to run parallel
    26.          //most of the time it will be a CPU bound operation
    27.  
    28.          Console.WriteLine("Hello World!");
    29.      }
    30. }      
    When you run this program you may see Press Enter to terminate! message first and then Hello World! as they both run in parallel, so it is not guaranteed which execute first.

    So,

    We can use Thread’s Join() method to halt our main thread until reference thread (that is “t” variable in our case) is truly shutdown.

    Another method to do this would be by using boolean IsAlive property of thread which gives instantaneous snapshot of thread’s state whether it is running or not. Like this,

    Code (CSharp):
    1. while ( t.IsAlive ) { }
    However, t.Join() is the recommended method.

    Here is an example

    Code (CSharp):
    1. using System;
    2. using System.Diagnostics;
    3. using System.Threading;
    4.  
    5. public class Example
    6. {
    7.     public static void Main()
    8.     {
    9.         //initialize a thread class object
    10.         //And pass your custom method name to the constructor parameter
    11.         Thread t = new Thread(SomeMethod);
    12.  
    13.         //start running your thread
    14.         t.Start();
    15.  
    16.         //while thread is running in parallel
    17.         //you can carry out other operations here
    18.  
    19.         //wait until Thread "t" is done with its execution.
    20.         t.Join();
    21.  
    22.         Console.WriteLine("Press Enter to terminate!");
    23.         Console.ReadLine();
    24.     }
    25.  
    26.     private static void SomeMethod()
    27.     {
    28.         //your code here that you want to run parallel
    29.         //most of the time it will be a CPU bound operation
    30.  
    31.         Console.WriteLine("Hello World!");
    32.     }
    33. }
    For more information. See post C# Thread.
     
    april_4_short likes this.