迭代器
迭代器用于逐步迭代集合。迭代器方法使用 yield return 返回元素,每次返回一个。到达 yield return 语句时,会记住当前在代码中的位置,下次调用迭代器方法时,将从该位置继续执行。
示例:
static void Main()
{
foreach (int number in SomeNumbers())
{
Console.Write(number.ToString() + " ");
}
// Output: 3 5 8
Console.ReadKey();
}
//迭代器函数
public static System.Collections.IEnumerable SomeNumbers()
{
yield return 3;
yield return 5;
yield return 8;
}
迭代器的返回类型可以是 IEnumerable, IEnumerable
创建可以用 foreach 遍历的集合类
static void Main()
{
DaysOfTheWeek days = new DaysOfTheWeek();
foreach (string day in days)
{
Console.Write(day + " ");
}
// Output: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey();
}
//集合类型,继承IEnumerable
public class DaysOfTheWeek : IEnumerable
{
private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
//编辑器隐式调用 GetEnumerator 方法
public IEnumerator GetEnumerator()
{
for (int index = 0; index < days.Length; index++)
{
// Yield each day of the week.
yield return days[index];
}
}
}
技术实现
- 即使将迭代器编写成方法,编译器也会将其转换为实际上是状态机的嵌套类。
- 为类和结构体创建迭代器时,不必实现整个 IEnumerator 接口。编译器检测到迭代器时,会自动生成接口的 Current, MoveNext 和 Dispose 方法。
LINQ (语言集成查询)
是一系列将查询功能集成到C#语言的技术统称。
反射
反射提供描述程序集,模块和类型的对象(Type类型)。可以从现有对象获取类型,获取到其成员信息并调用,还可以动态地创建类型的实例。如果代码中使用了特性(Attributes),则可以利用反射访问他们。
示例:
using System;
//测试类
public class ExampleClass
{
private string name = "";
public void Method(){}
}
//从现有对象获取类型
ExampleClass a = new ExampleClass();
Type type = a.GetType();
Console.WriteLine(type); //输出结果:ExampleClass
//动态创建类型的实例
Type type = Type.GetType("ExampleClass");
ExampleClass b = Activator.CreateInstance(type) as ExampleClass;
线程处理
借助多线程处理,可以提高应用程序的响应能力。
进程和线程
进程是一种正在执行的程序,操作系统使用进程来分隔正在执行的应用程序;
线程是操作系统向进程分配处理时间的基本单元。
默认情况下,.NET程序由单个线程(通常称主线程)启动。可以创建其他线程与主线程并行执行代码,这些线程通常称工作线程。
创建线程
线程一旦启动,就无需保留对 Thread 对象的引用,线程可以继续执行到线程结束。
下面的代码示例新建两个线程,以对另一个对象调用实例和静态方法
using System;
using System.Threading;
public class ServerClass
{
// The method that will be called when the thread is started.
public void InstanceMethod()
{
Console.WriteLine(
"ServerClass.InstanceMethod is running on another thread.");
// Pause for a moment to provide a delay to make
// threads more apparent.
Thread.Sleep(3000);
Console.WriteLine(
"The instance method called by the worker thread has ended.");
}
public static void StaticMethod()
{
Console.WriteLine(
"ServerClass.StaticMethod is running on another thread.");
// Pause for a moment to provide a delay to make
// threads more apparent.
Thread.Sleep(5000);
Console.WriteLine(
"The static method called by the worker thread has ended.");
}
}
public class Simple
{
public static void Main()
{
ServerClass serverObject = new ServerClass();
// Create the thread object, passing in the
// serverObject.InstanceMethod method using a
// ThreadStart delegate.
Thread InstanceCaller = new Thread(
new ThreadStart(serverObject.InstanceMethod));
// Start the thread.
InstanceCaller.Start();
Console.WriteLine("The Main() thread calls this after "
+ "starting the new InstanceCaller thread.");
// Create the thread object, passing in the
// serverObject.StaticMethod method using a
// ThreadStart delegate.
Thread StaticCaller = new Thread(
new ThreadStart(ServerClass.StaticMethod));
// Start the thread.
StaticCaller.Start();
Console.WriteLine("The Main() thread calls this after "
+ "starting the new StaticCaller thread.");
}
}
// The example displays the output like the following:
// The Main() thread calls this after starting the new InstanceCaller thread.
// The Main() thread calls this after starting the new StaticCaller thread.
// ServerClass.StaticMethod is running on another thread.
// ServerClass.InstanceMethod is running on another thread.
// The instance method called by the worker thread has ended.
// The static method called by the worker thread has ended.
暂停和中断线程
- Thread.Sleep 是一种让当前线程进入睡眠(暂停/受阻止)状态的静态方法,一个线程不能调用另一个线程上的 Thread.Sleep
- 可以通过对受阻止的线程调用 Thread.Interrupt 方法中断线程,从而抛出 ThreadInterruptedException ,线程应该捕获 ThreadInterruptedException 并执行任何适用于继续工作的操作。如果线程忽略该异常,该线程停止。
调用 Thread.Interrupt 中断一个未处于受阻止状态的线程,则线程在未受阻止前不会中断,若永不受阻,则永不中断。
- Thread.Abort 方法用于永久停止线程,目标线程可以捕获 ThreadAbortException。一旦线程停止,就无法再重启。
死锁和争用条件
死锁
两个线程都尝试锁定另外一个线程已锁定的资源时,就会发生死锁,两个线程都不能继续执行。
争用条件
争用条件是指程序的结果取决于多个线程中哪一个先到达指定代码块时出现的一种bug,多次运行程序会产生不同的结果,并且无法预测结果。
编写每一行代码时,都必须考虑以下情况会发生什么情况:一个线程在执行该代码前,其他线程抢先执行了该代码。
静态成员和静态构造函数
在类的构造函数完成之前,该类不会初始化。为防止对未初始化的类型执行代码,在类构造函数完成运行之前,公共语言运行时会禁止从其他线程到类的静态成员的所有调用。
一般性建议
- 不要使用 Thread.Abort 终止其他线程。对另一个线程调用 Abort 无异于引发改线程的异常,也不知道该线程已处理到哪个位置。
- 不要从主程序控制其他线程的执行。
- 不要将类型用作锁定对象。
- 锁定实例要谨慎。如 lock(this) ,如果应用程序中不属于该类型的其他代码锁定了该对象,则会发生死锁。
- 对于简单的状态更改,请考虑使用 Interlocked 类的方法,而不是 lock 语句。Interlocked 类提升了更新(必须是原子操作)的性能。
示例1,状态变量是递增的
lock(lockObject)
{
myField++;
}
若要提升性能,可使用 Increment 方法代替 lock 语句
System.Threading.Interlocked.Increment(myField);
示例2,当类型变量为 null 时才会将其更新
if (x == null)
{
lock (lockObject)
{
if (x == null)
{
x = y;
}
}
}
可以改用 CompareExchange 方法提升性能
System.Threading.Interlocked.CompareExchange(ref x, y, null);
托管线程池
ThreadPool 类为你的应用程序提供一个受系统管理的辅助线程池,从而使你更专注应用程序任务,而非线程管理。如果有需要后台处理的短任务,托管的线程池则是使用多线程的简便方法。
线程池线程使后台线程。每个进程只有一个线程池。一旦线程池中的线程完成任务,它将返回到等待线程队列中。这时即可开始重用它,通过这种重复使用,可以避免为每个任务创建新线程的开销。
使用线程池
最简单方式是使用任务并行库(TPL)。默认情况下TPL类型(例如 Task 和 Task
何时不使用线程池线程
有几种场景,适合创建自己的线程而非使用线程池:
- 需要一个前台线程
- 需要具有特定优先级的线程
- 拥有会导致线程长时间阻塞的任务。线程池具有最大线程数,因此大量被阻塞的线程池线可能会阻止任务启动
- 需要将一个线程专用于一项任务