Unity basic learning seventeen, C# advanced attributes: unsafe code, anonymous methods (Anonymous methods)

1. C# unsafe code

C# allows the use of pointer variables in functions when a block of code is marked with the unsafe modifier. Unsafe code , or unmanaged code, is a block of code that uses pointer variables.

1.1 Pointer variables

A pointer is a variable whose value is the address of another variable, that is, the direct address of a memory location. Just like any other variable or constant, you must declare a pointer before using it to store the address of another variable.

The general form of a pointer variable declaration is:

type* var-name;

The following are examples of pointer type declarations:

int* p p is a pointer to an integer.
double* p p is a pointer to a double precision number.
float* p p is a pointer to a floating point number.
int** p p is a pointer to a pointer to an integer.
int*[] p p is a one-dimensional array of pointers to integers.
char* p p is a pointer to a character.
void* p p is a pointer to an unknown type.

When declaring multiple pointers in the same declaration, the asterisk * is only written with the underlying type; it is not used as a prefix to each pointer name. For example: 

int* p1, p2, p3;     // 正确  
int *p1, *p2, *p3;   // 错误 

The following example illustrates   the use of pointers when the unsafe modifier is used in C#:

using System;
namespace UnsafeCodeApplication
{
    class Program
    {
        static unsafe void Main(string[] args)
        {
            int var = 20;
            int* p = &var;
            Console.WriteLine("Data is: {0} ",  var);
            Console.WriteLine("Address is: {0}",  (int)p);
            Console.ReadKey();
        }
    }
}
Data is: 20
Address is: 99215364

1.2 Retrieving Data Values ​​Using Pointers

You can use  the ToString()  method to retrieve the data stored at the location referenced by the pointer variable. The following example demonstrates this:

using System;
namespace UnsafeCodeApplication
{
   class Program
   {
      public static void Main()
      {
         unsafe
         {
            int var = 20;
            int* p = &var;
            Console.WriteLine("Data is: {0} " , var);
            Console.WriteLine("Data is: {0} " , p->ToString());
            Console.WriteLine("Address is: {0} " , (int)p);
         }
         Console.ReadKey();
      }
   }
}
Data is: 20
Data is: 20
Address is: 77128984

1.3 Passing pointers as method parameters

using System;
namespace UnsafeCodeApplication
{
   class TestPointer
   {
      public unsafe void swap(int* p, int *q)
      {
         int temp = *p;
         *p = *q;
         *q = temp;
      }

      public unsafe static void Main()
      {
         TestPointer p = new TestPointer();
         int var1 = 10;
         int var2 = 20;
         int* x = &var1;
         int* y = &var2;
         
         Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);
         p.swap(x, y);

         Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);
         Console.ReadKey();
      }
   }
}
Before Swap: var1: 10, var2: 20
After Swap: var1: 20, var2: 10

1.4 Using pointers to access array elements

In C#, an array name and a pointer to the same data type as the array data are different variable types. For example, int *p and int[] p are different types. You can increment pointer variable p because it is not fixed in memory but array address is fixed in memory so you cannot increment array p.

So, if you need to access array data using a pointer variable, you can use the fixed  keyword to fix the pointer as we usually do in C or C++  .

using System;
namespace UnsafeCodeApplication
{
   class TestPointer
   {
      public unsafe static void Main()
      {
         int[]  list = {10, 100, 200};
         fixed(int *ptr = list)

         /* 显示指针中数组地址 */
         for ( int i = 0; i < 3; i++)
         {
            Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
            Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
         }
         Console.ReadKey();
      }
   }
}
Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200

1.5 Compile unsafe code

In order to compile unsafe code, you must switch to the command line compiler specifying  the /unsafe  command line.

For example, to compile a program named prog1.cs that contains unsafe code, enter the command at the command line:

csc /unsafe prog1.cs

If you are using Visual Studio IDE then you need to enable unsafe code in project properties.

Proceed as follows:

  • Open the project properties by double-clicking the properties node in the Solution Explorer .
  • Click on  the Build  tab.
  • Select the option " Allow unsafe code ".

1.6 fixed keyword

Since the storage of variables declared in C# in memory is managed by the garbage collector ; therefore, a variable (such as a large array) may be moved to other locations in memory during runtime. If the memory address of a variable changes, then pointers are meaningless.

The solution is to use the fixed keyword to fix the variable position without moving.

static unsafe void Main(string[] args)
{
  fixed(int *ptr = int[5])  {//...}
}

 stackalloc

In an unsafe unsafe environment, we can allocate memory on the stack through stackalloc, because the memory allocated on the stack is not managed by the memory manager, so its corresponding pointer does not need to be fixed.

static unsafe void Main(string[] args)
{
  int *ptr = stackalloc int[1] ;
}

2. C# multithreading

A thread  is defined as a program's path of execution. Each thread defines a unique flow of control. If your application involves complex and time-consuming operations, it is often beneficial to have different execution paths for threads, with each thread performing specific work.

Threads are lightweight processes . A common example of the use of threads is the implementation of parallel programming in modern operating systems. Using threads saves wasted CPU cycles and improves application efficiency.

The programs we've written so far run as a single process with a single thread as the running instance of the application. However, such sub-applications can only perform one task at a time. It can be divided into smaller threads in order to perform multiple tasks simultaneously.

2.1 Thread life cycle

The thread lifecycle begins when an object of the System.Threading.Thread class is created and ends when the thread is terminated or completes execution.

The various states in the thread lifecycle are listed below:

  • Not started state : The state when the thread instance is created but the Start method is not called.
  • Ready state : The state when a thread is ready to run and waiting for CPU cycles.
  • Non-runnable state : A thread is not runnable in the following situations:

    • The Sleep method has been called
    • The Wait method has been called
    • Blocking on I/O operations
  • Dead state : The condition when a thread has completed execution or has been aborted.

2.2 Main thread

In C#, the System.Threading.Thread  class is used for thread work. It allows creation and access to individual threads in a multithreaded application. The first thread executed in a process is called the main thread .

When a C# program starts executing, the main thread is created automatically.  Threads created using  the Thread class are called by sub-threads of the main thread.  You can access threads using the CurrentThread property of the Thread class  .

The following program demonstrates the execution of the main 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();
        }
    }
}

When the above code is compiled and executed, it produces the following result:

This is MainThread

2.3 Properties and methods commonly used in the Thread class

The following table lists   some commonly used  properties of the Thread class :

Attributes describe
CurrentContext Gets the current context in which the thread is executing.
CurrentCulture Gets or sets the culture of the current thread.
CurrentPrincipal Gets or sets the thread's current leader (for role-based security).
CurrentThread Get the currently running thread.
CurrentUICulture Gets or sets the current culture used by the resource manager to find culture-specific resources at runtime.
ExecutionContext Gets an ExecutionContext object that contains information about the current thread's various contexts.
IsAlive Gets a value indicating the execution state of the current thread.
IsBackground Gets or sets a value indicating whether a thread is a background thread.
IsThreadPoolThread Gets a value indicating whether the thread belongs to the managed thread pool.
ManagedThreadId Get the unique identifier for the currently managed thread.
Name Gets or sets the name of the thread.
Priority Gets or sets a value indicating the thread's scheduling priority.
ThreadState Gets a value containing the state of the current thread.

The following table lists   some commonly used  methods of the Thread class :

serial number Method name & description
1 public void Abort()
Throws a ThreadAbortException on the thread calling this method to begin the process of terminating this thread. Calling this method normally terminates the thread.
2 public static LocalDataStoreSlot AllocateDataSlot()
allocates unnamed data slots on all threads. For better performance, use fields marked with the ThreadStaticAttribute attribute instead.
3 public static LocalDataStoreSlot AllocateNamedDataSlot( string name)
Allocates named data slots on all threads. For better performance, use fields marked with the ThreadStaticAttribute attribute instead.
4 public static void BeginCriticalRegion()
Notifies the host that execution is about to enter a region of code where a thread abort or the effects of an unhandled exception could jeopardize other tasks in the application domain.
5 public static void BeginThreadAffinity()
Notifies the host that managed code is about to execute instructions that depend on the identity of the current physical operating system thread.
6 public static void EndCriticalRegion()
Notifies the host that execution is about to enter a region of code where a thread abort or an unhandled exception affects only the current task.
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 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。此方法有不同的重载形式。
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 )

读取字段值。无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值。此方法有不同的重载形式。这里只给出了一些形式。
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 )

立即向字段写入一个值,以使该值对计算机中的所有处理器都可见。此方法有不同的重载形式。这里只给出了一些形式。
23 public static bool Yield()
导致调用线程执行准备好在当前处理器上运行的另一个线程。由操作系统选择要执行的线程。

2.4创建线程 

线程是通过扩展 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

2.5管理线程

Thread 类提供了各种管理线程的方法。

下面的实例演示了 sleep() 方法的使用,用于在一个特定的时间暂停线程。

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");
            // 线程暂停 5000 毫秒
            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

2.6 销毁线程

Abort() 方法用于销毁线程。

通过抛出 threadabortexception 在运行时中止线程。这个异常不能被捕获,如果有 finally 块,控制会被送至 finally 块。

下面的程序说明了这点:

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            try
            {

                Console.WriteLine("Child thread starts");
                // 计数到 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();
            // 停止主线程一段时间
            Thread.Sleep(2000);
            // 现在中止子线程
            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 

Guess you like

Origin blog.csdn.net/u013617851/article/details/124439997