36、反射与线程编程:原理、应用与实践

反射与线程编程:原理、应用与实践

1. 反射与后期绑定

反射允许在运行时绑定方法,提供了在运行时选择绑定对象并以编程方式调用的灵活性。以调用 System.Math 类的 Cos() 方法为例,详细介绍反射的使用。

1.1 获取类型信息

首先,需要获取 System.Math 类的类型信息:

Type theMathType = Type.GetType("System.Math");
1.2 创建对象实例

使用 Activator 类的静态方法动态加载类的实例。由于 Cos() 是静态方法,无需创建 System.Math 的实例( System.Math 没有公共构造函数)。 Activator 类有四个静态方法:
| 方法 | 描述 |
| ---- | ---- |
| CreateComInstanceFrom | 创建 COM 对象的实例 |
| CreateInstanceFrom | 根据特定程序集和类型名称创建对象的引用 |
| GetObject | 用于封送对象 |
| CreateInstance | 创建对象的本地或远程实例,例如 Object theObj = Activator.CreateInstance(someType); |

1.3 获取方法信息

在调用方法之前,需要从 Type 对象中获取所需的方法。以 Cos() 方法为例,其签名包括方法名 Cos 和参数类型(一个 double 类型的参数)。调用 GetMethod() 方法时,需要传入方法名和参数类型数组:

Type[] paramTypes = new Type[1];
paramTypes[0] = Type.GetType("System.Double");
MethodInfo CosineInfo = theMathType.GetMethod("Cos", paramTypes);
1.4 调用方法

获取 MethodInfo 对象后,可以调用该方法。调用时需要传入要调用方法的对象和参数的实际值数组。由于 Cos() 是静态方法,传入 theMathType

Object[] parameters = new Object[1];
parameters[0] = 45 * (Math.PI / 180); // 45 度转换为弧度
Object returnVal = CosineInfo.Invoke(theMathType, parameters);

以下是完整的示例代码:

using System;
using System.Reflection;

namespace DynamicallyInvokingAMethod
{
    public class Tester
    {
        public static void Main()
        {
            Type theMathType = Type.GetType("System.Math");
            Type[] paramTypes = new Type[1];
            paramTypes[0] = Type.GetType("System.Double");
            MethodInfo CosineInfo = theMathType.GetMethod("Cos", paramTypes);
            Object[] parameters = new Object[1];
            parameters[0] = 45 * (Math.PI / 180);
            Object returnVal = CosineInfo.Invoke(theMathType, parameters);
            Console.WriteLine("The cosine of a 45 degree angle {0}", returnVal);
        }
    }
}

输出结果:

The cosine of a 45 degree angle 0.707106781186548
2. 线程编程基础

线程负责在单个应用程序中实现多任务处理。 System.Threading 命名空间提供了丰富的类和接口来管理多线程编程。

2.1 线程的创建与启动

创建线程的最简单方法是创建 Thread 类的新实例。 Thread 构造函数接受一个委托实例,CLR 提供了 ThreadStart 委托类,该委托指向一个无参数、无返回值的方法。

以下是创建两个工作线程的示例:

using System;
using System.Threading;

namespace UsingThreads
{
    class Tester
    {
        static void Main()
        {
            Tester t = new Tester();
            Console.WriteLine("Hello");
            t.DoTest();
        }

        public void DoTest()
        {
            Thread t1 = new Thread(new ThreadStart(Incrementer));
            Thread t2 = new Thread(new ThreadStart(Decrementer));
            t1.Start();
            t2.Start();
        }

        public void Incrementer()
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine("Incrementer: {0}", i);
            }
        }

        public void Decrementer()
        {
            for (int i = 1000; i >= 0; i--)
            {
                Console.WriteLine("Decrementer: {0}", i);
            }
        }
    }
}

输出结果显示处理器在两个线程之间切换:

Incrementer: 102
Incrementer: 103
Incrementer: 104
Incrementer: 105
Incrementer: 106
Decrementer: 1000
Decrementer: 999
Decrementer: 998
Decrementer: 997

线程调度器会根据多种因素(如处理器速度、其他程序对处理器的需求等)分配每个线程的执行时间。

2.2 线程的加入(Joining)

当一个线程需要等待另一个线程完成工作时,可以使用 Join() 方法。例如,在 Main() 方法中等待其他线程结束后再输出最终消息:

foreach (Thread myThread in myThreads)
{
    myThread.Join();
}
Console.WriteLine("All my threads are done.");
2.3 线程的阻塞(Sleep)

Thread 类提供了静态方法 Sleep ,用于暂停线程的执行。该方法有两个重载版本,一个接受 int 类型的参数,表示暂停的毫秒数;另一个接受 TimeSpan 对象。

以下是在 Incrementer 方法中添加 Sleep 方法的示例:

for (int i = 0; i < 1000; i++)
{
    Console.WriteLine("Incrementer: {0}", i);
    Thread.Sleep(1);
}

添加 Sleep 方法后,输出结果会发生显著变化,每个线程在另一个线程输出一个值后都有机会执行。

graph TD;
    A[开始] --> B[创建线程 t1 和 t2];
    B --> C[启动线程 t1 和 t2];
    C --> D{t1 执行};
    D --> E{t2 执行};
    E --> F{线程调度};
    F --> D;
    F --> E;
    D --> G[线程结束];
    E --> G;
    G --> H[输出完成消息];
2.4 线程的终止

通常情况下,线程在执行完函数后会自然结束。但在某些情况下,需要手动终止线程。以下是几种常见的终止线程的方法:
- 设置标志位 :设置一个 KeepAlive 布尔标志,线程定期检查该标志。当标志状态改变时,线程自行停止。
- 调用 Thread.Interrupt :请求线程自行终止。
- 调用 Thread.Abort :在紧急情况下,调用 Abort 方法会抛出 ThreadAbortException 异常,线程可以捕获该异常并进行相应处理。

以下是一个中断线程的示例代码:

using System;
using System.Threading;

namespace InterruptingThreads
{
    class Tester
    {
        static void Main()
        {
            Tester t = new Tester();
            t.DoTest();
        }

        public void DoTest()
        {
            Thread[] myThreads =
            {
                new Thread(new ThreadStart(Decrementer)),
                new Thread(new ThreadStart(Incrementer)),
                new Thread(new ThreadStart(Decrementer)),
                new Thread(new ThreadStart(Incrementer))
            };

            int ctr = 1;
            foreach (Thread myThread in myThreads)
            {
                myThread.IsBackground = true;
                myThread.Start();
                myThread.Name = "Thread" + ctr.ToString();
                ctr++;
                Console.WriteLine("Started thread {0}", myThread.Name);
                Thread.Sleep(50);
            }

            myThreads[0].Interrupt();
            myThreads[1].Abort();

            foreach (Thread myThread in myThreads)
            {
                myThread.Join();
            }

            Console.WriteLine("All my threads are done.");
        }

        public void Decrementer()
        {
            try
            {
                for (int i = 100; i >= 0; i--)
                {
                    Console.WriteLine("Thread {0}. Decrementer: {1}", Thread.CurrentThread.Name, i);
                    Thread.Sleep(1);
                }
            }
            catch (ThreadAbortException)
            {
                Console.WriteLine("Thread {0} aborted! Cleaning up...", Thread.CurrentThread.Name);
            }
            catch (System.Exception e)
            {
                Console.WriteLine("Thread has been interrupted ");
            }
            finally
            {
                Console.WriteLine("Thread {0} Exiting. ", Thread.CurrentThread.Name);
            }
        }

        public void Incrementer()
        {
            try
            {
                for (int i = 0; i < 100; i++)
                {
                    Console.WriteLine("Thread {0}. Incrementer: {1}", Thread.CurrentThread.Name, i);
                    Thread.Sleep(1);
                }
            }
            catch (ThreadAbortException)
            {
                Console.WriteLine("Thread {0} aborted!", Thread.CurrentThread.Name);
            }
            catch (System.Exception e)
            {
                Console.WriteLine("Thread has been interrupted");
            }
            finally
            {
                Console.WriteLine("Thread {0} Exiting. ", Thread.CurrentThread.Name);
            }
        }
    }
}

在这个示例中,创建了四个线程,在启动线程前将 IsBackground 属性设置为 true 。启动线程后,中断第一个线程并终止第二个线程,最后等待所有线程结束并输出完成消息。

3. 线程同步

当多个线程访问有限资源(如数据库连接、文件等)时,需要进行同步操作,以确保同一时间只有一个线程可以访问该资源。

3.1 同步的必要性

以飞机上的洗手间为例,为了保证同一时间只有一人使用洗手间,需要在门上设置锁。在编程中,当多个线程需要访问共享资源时,也需要进行类似的操作,以避免数据竞争和不一致的问题。

3.2 同步的实现

在编程中,可以使用锁机制来实现同步。当一个线程访问资源时,将资源锁定,其他线程需要等待该线程释放锁后才能访问资源。

以下是一个简单的同步示例:

using System;
using System.Threading;

class Program
{
    private static readonly object lockObject = new object();
    private static int sharedResource = 0;

    static void Main()
    {
        Thread t1 = new Thread(IncrementResource);
        Thread t2 = new Thread(IncrementResource);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Shared resource value: " + sharedResource);
    }

    static void IncrementResource()
    {
        for (int i = 0; i < 1000; i++)
        {
            lock (lockObject)
            {
                sharedResource++;
            }
        }
    }
}

在这个示例中,使用 lock 语句对共享资源 sharedResource 进行同步。 lock 语句会获取 lockObject 的锁,确保同一时间只有一个线程可以执行 lock 块内的代码。

graph TD;
    A[开始] --> B[创建线程 t1 和 t2];
    B --> C[启动线程 t1 和 t2];
    C --> D{t1 请求锁};
    D --> E{获取锁};
    E --> F{t1 访问共享资源};
    F --> G{t1 释放锁};
    G --> H{t2 请求锁};
    H --> I{获取锁};
    I --> J{t2 访问共享资源};
    J --> K{t2 释放锁};
    D --> L{t2 请求锁};
    L --> M{等待 t1 释放锁};
    M --> I;
    F --> N{线程结束};
    J --> N;
    N --> O[输出共享资源值];

总结

反射和线程编程是编程中重要的技术。反射允许在运行时动态绑定和调用方法,提供了更大的灵活性;线程编程则可以实现多任务处理,提高程序的性能和响应性。在使用线程编程时,需要注意线程的创建、管理、终止和同步,以避免出现数据竞争和不一致的问题。通过合理运用这些技术,可以开发出更加高效、稳定的程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值