反射与线程编程:原理、应用与实践
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[输出共享资源值];
总结
反射和线程编程是编程中重要的技术。反射允许在运行时动态绑定和调用方法,提供了更大的灵活性;线程编程则可以实现多任务处理,提高程序的性能和响应性。在使用线程编程时,需要注意线程的创建、管理、终止和同步,以避免出现数据竞争和不一致的问题。通过合理运用这些技术,可以开发出更加高效、稳定的程序。
超级会员免费看
2971

被折叠的 条评论
为什么被折叠?



