@TOC
前言 搞编程的,基础不牢,地动山摇,多多复习哦,与诸君共勉。
一、特征【ObsoleteAttribute】、程序 1.[Obsolete(“提示信息”, ture/false)]:false/true代表是否停止编译,true代表。编译器发出错误提示并停止遍历,否则只发出提示信息不停止编译。提醒旧函数过时,使用新函数: 1 2 3 4 5 [Obsolete("该函数已经过时,请使用新函数NewDoSomething()" , false) ]public void DoSomething () { }
2..NET中预定义了200多个特性,细致信息在System.Reflection命名空间中的类
也可以自定义特性类,咱们用特征修饰哪个程序就用[]括起来放在程序前面。自定义的特性中的构造函数中的参数是必选参数,特性中的公有变量是可选参数
自定义特性时可以被其他特性修饰:1 2 3 4 5 6 7 用来规定特性可作用于哪些程序元素,可以用多个,中间用|连接; 有Inherited能否被继承;AllowMultiple表示能够对同一元素多次应用特性 [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true) ]internal class Program {
3.程序集:我们写好的代码最终都被编译器编译为很多程序集一种逻辑上的划分
两种最常见的程序集.exe和.dll
.exe:可执行文件。有一个主程序入口,可运行
.dll:动态链接库文件。无主程序入口,不可运行,智能让其他程序调用
二、反射:获取类型和程序集的信息.NET在System.Reflection命名空间中定义了一系列反射类,和System.Type类一起提供了反射功能
System.Reflection.Assembly类是为控制程序集【获取程序集元数据信息】而设计。在获取程序集元数据之前需要先用Assembly对象的Load()方法把程序集加载到内存
动态加载、动态调用、实现插件、System.Activator.CreateInstance(XxxType, null);XxxType代表咱们用.GetType获取的类,null代表构造函数的参数、xxx as xxx,强制类型转换,前面大后面小【强制类型转换是为了得到对象的方法,咱们也可以用Type.GetMethod(“欲调用方法名”)】
三、多线程 1.创建一个线程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 System.Threading.Thread:private static void EntryPointMethod () { }public delegate void ThreadStart () ; ThreadStart entryPoint = new ThreadStart(EntryPointMethod); Thread WorkThread = new Thread(entryPoint); 注意事项:除了用委托传递线程的入口函数之外,还可以通过匿名函数创建线程,将上面三个步骤合并为一步。并且解决了咱不能使用线程前面定义的变量,也就是入口函数没有参数和返回值的缺点 Thread DrawGraphThread = new Thread(delegate () { } )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 例子,namespace WindowsFormsApp1 { public partial class Form1 : Form { #region Form1构造函数 public Form1 () { InitializeComponent(); } #endregion #region 图形绘制函数 private void DrawGraph () { int loop = 0 ; int sect = 0 ; float [] x = new float [31 ]; float [] y = new float [31 ]; while (loop <= 10000 ) { sect = (sect + 1 ) % 25 + 1 ; Graphics graphics = this .CreateGraphics(); for (int i = 0 ; i < sect; i++) { x[i] = ((float )(150 * Math.Cos(i * 2 * Math.PI / sect) + 150 )); y[i] = (float )(150 * Math.Sin(i * 2 * Math.PI / sect) + 150 ); } for (int m = 0 ; m < sect - 1 ; m++) { for (int n = 0 ; n < sect; n++) { graphics.DrawLine(Pens.Blue, x[m], y[m], x[n], y[n]); } } Thread.Sleep(200 ); graphics.Clear(this .BackColor); loop++; } } #endregion #region 声明工作线程,并创建四个按钮的事件 Thread DrawGraphThread; private void startThreadButton_Click (object sender, EventArgs e ) { ThreadStart entryPoint = new ThreadStart(DrawGraph); DrawGraphThread = new Thread(entryPoint); DrawGraphThread.Start(); startThreadButton.Enabled = false ; suspendThreadButton.Enabled = true ; resumeThreadButton.Enabled = false ; abortThreadButton.Enabled = true ; } private void suspendThreadButton_Click (object sender, EventArgs e ) { DrawGraphThread.Suspend(); suspendThreadButton.Enabled = false ; resumeThreadButton.Enabled = true ; abortThreadButton.Enabled = false ; } private void resumeThreadButton_Click (object sender, EventArgs e ) { DrawGraphThread.Resume(); resumeThreadButton.Enabled = false ; suspendThreadButton.Enabled = true ; abortThreadButton.Enabled = true ; } private void abortThreadButton_Click (object sender, EventArgs e ) { DrawGraphThread.Abort(); startThreadButton.Enabled = true ; suspendThreadButton.Enabled = false ; resumeThreadButton.Enabled = false ; abortThreadButton.Enabled = false ; } #endregion }
2.线程的优先级可以通过Thread.Priority设置设置,Priority属性是一个ThreadPriority型枚举,5个优先级
没明确指定则两个线程优先级相同均为默认值Normal,所以交替执行【被执行的几率大概相等】。而且,在默认情况下,主线程Main和工作线程的优先级相同,也是交替进行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Thread ThreadA = new Thread(delegate () { for (int i = 0 ; i <= 100000000 ; i++) { if (i % 1000000 == 0 ) { Console.WriteLine('A' ); } } }); Thread ThreadB = new Thread(delegate () { for (int i = 0 ; i <= 100000000 ; i++) { if (i % 1000000 == 0 ) { Console.WriteLine('B' ); } } }); ThreadA.Start(); ThreadB.Start();
调整优先级后,只是说明优先级高的线程占有更多的CPU事件,cpu会执行哪个看CPU的心情
3.线程的同步:需要两个线程协同工作才能完成一项任务的情况,不协同最终结果就会出错
多个线程共享的资源时临界资源;访问临界资源的代码叫做临界区;
一个例子:结果出错,用互锁、管程等修好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 internal class Program { private static char buffer; private static void Main (string [] args ) { Thread Writer = new Thread(delegate () { string sentence = "无可奈何花落去,似曾相识燕归来,i love min." ; for (int i = 0 ; i < 24 ; i++) { buffer = sentence[i]; Thread.Sleep(26 ); } }); Thread Reader = new Thread(delegate () { for (int i = 0 ; i < 24 ; i++) { char ch = buffer; Console.Write(ch); Thread.Sleep(20 ); } }); Writer.Start(); Reader.Start(); }
.NET框架保证线程同步的同步类【看下面例子】
Interlocked互锁
用Interlocked互锁的解决思路:为了实现同步,线程Writer在写入字符前先检查缓冲区满否?,满则等待,直到缓冲区数据被进程Reader读取;缓冲区空就写入,写入后就把缓冲区标记为满
numberOfUsedSpace计数器。通过System.Threading.Interlocked类控制计数器,实现进程的同步
运行结果完全正确
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 internal class Program { private static char buffer; private static long numberOfUsedSpace = 0 ; private static void Main (string [] args ) { Thread Writer = new Thread(delegate () { string sentence = "无可奈何花落去,似曾相识燕归来,i love min." ; for (int i = 0 ; i < 24 ; i++) { while (Interlocked.Read(ref numberOfUsedSpace) == 1 ) { Thread.Sleep(10 ); } buffer = sentence[i]; Interlocked.Increment(ref numberOfUsedSpace); } }); Thread Reader = new Thread(delegate () { for (int i = 0 ; i < 24 ; i++) { while (Interlocked.Read(ref numberOfUsedSpace) == 0 ) { Thread.Sleep(10 ); } char ch = buffer; Console.Write(ch); Interlocked.Decrement(ref numberOfUsedSpace); } }); Writer.Start(); Reader.Start(); } }
- Monitor管程
- 用Monitor的解决思路:用独占锁方式控制线程同步,只有获得独占锁的线程才能访问临界资源。一线程进入临界区时先调用Monitor类的Enter()方法尝试获取临界资源的独占锁若独占锁已被其他线程占用这个线程就进入等待状态。前面线程在执行后面线程只能等待,睡在临界资源上。Monitor会记录所有睡在临界资源上的线程,执行完了退出临界区时需要调用Monitor.Pulse()方法唤醒睡眠在临界资源的线程
- 运行结果完全正确:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 internal class Program { private static char buffer; private static object lockForBuffer = new object (); private static void Main (string [] args ) { Thread Writer = new Thread(delegate () { string sentence = "无可奈何花落去,似曾相识燕归来,i love min." ; for (int i = 0 ; i < 30 ; i++) { try { Monitor.Enter(lockForBuffer); buffer = sentence[i]; Monitor.Pulse(lockForBuffer); Monitor.Wait(lockForBuffer); } catch (System.Threading.ThreadInterruptedException) { Console.WriteLine("Writer写线程被中止" ); ; } finally { Monitor.Exit(lockForBuffer); } } }); Thread Reader = new Thread(delegate () { for (int i = 0 ; i < 30 ; i++) { try { Monitor.Enter(lockForBuffer); char ch = buffer; Console.Write(ch); Monitor.Pulse(lockForBuffer); Monitor.Wait(lockForBuffer); } catch (System.Threading.ThreadInterruptedException) { Console.WriteLine("Reader读线程被中止" ); ; } finally { Monitor.Exit(lockForBuffer); } } }); Writer.Start(); Reader.Start(); } }
- 注意事项:
- Monitor类只能锁定引用类型对象而不能锁定值类型变量。这是因为当参数为值类型变量时,每调用依次Monitor.Enter()方法时值类型要进行一次装箱操作得到一个新Object对象,Monitor类不同操作用于不同对象是不能保证原子性的
- 为确临界区时临界资源得到释放,应该在try中用Monitor类的代码并在finally中调用Monitor.Exit()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 try { Monitor.Enter(要锁定的对象); }finally { Monitor.Exit(要锁定的对象); } 等价写法lock (要锁定的对象) { }
- 一个线程在某资源上放了一把锁,其他访问该资的线程只能暂停,程序效率降低,所以加锁之前想清楚
- Mutex互斥体
- 排他性的使用共享资源,也就是一个资源一次只能被一个线程服务的过程或行为叫做线程间的互斥。互斥是一种特殊的线程同步。
- 注意事项:
- 和Monitor类似,只有获取Mutex对象所属权的线程才能进入临界区
- 虽然用Mutex类要比使用Monitor类消耗更多的系统资源,但是用Mutex类可以跨越应用程序边界,在多个应用程序之间进行同步【系统互斥体】。还有局部互斥体,只能在创建Mutex对象的程序中用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 在线程中创建互斥体: Thread ThreadA = new Thread(delegate () { Mutex fileMutex = new Mutex(false , "MutexForTimeRecordFile" ); }); 第一个参数代表创建者是否具有初始所属权,第二个参数代表互斥体的系统名称, OS根据互斥体的系统名称辨别互斥体,只要有相同系统名称,就是同一个系统互斥体 创建完了后就是在trycatch语句中用了 ...try { fileMutex.WaitOne(); ... } ...finally { fileMutex.ReleaseMutex(); } ...
4.死锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 解决方法:让两个或多个线程以相同顺序访问 临界资源即可internal class Program { private static object A = new object (); private static object B = new object (); private static void Main (string [] args ) { Thread WriterThread = new Thread(delegate () { lock (A) { lock (B) { } } }); Thread ReaderThread = new Thread(delegate () { lock (A) { lock (B) { } } }); Writer.Start(); Reader.Start(); } }
5.线程池
线程池:System.Threading.ThreadPool类,如果程序中包含很多简单不需要特殊控制的线程就可用ThreadPool类
一个应用程序最多一个线程池,
如果向对线程进行更多特殊控制,就别用线程池。这三种情况不宜用ThreadPool类而用单独的Thread类
线程执行很多时间
需要为线程指定详细的优先级
执行过程中需要对线程进行睡眠、挂起等操作
.NET中线程池中线程数目默认最少1最多25可以用GetMaxThreads()、GetMinThreadsSetMaxThreads()、SetMinThreads()获取线程数目上限和下限
四、集合 1..NET集合类【CL类库中】
System.Array类:优点是可按序找数,缺点是大小固定
System.Collections
2.IEnumerable接口,直接或间接实现了IEnumerable接口的类,在遍历时foreach会自动调用IEnumerable接口中的GetEnumerator()方法去获取元素。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 IEnumerable接口,直接或间接实现了IEnumerable接口的 类,在遍历时foreach 会自动调用IEnumerable接口中的 GetEnumerator()方法去获取元素。public interface IEnumerable { IEnumerator GetIEnumerator () ; }public interface IEnumerator { bool MoveNext () ; object Currect{ get ;} void Reset () ; }
3.IEnumerable 接口并不是我们看到的只有一个方法,它还有 4 个扩展方法。其中 Cast() 和 OfType() 这 2 个方法非常实用。而 OfType 比 Cast 更加强大,它除了进行强制转换外还可以实现类型的过滤。从结果中可以看到第一个 foreach 遇到 int 类型后由于无法转换而报出 InvalidCastException 异常,而使用 OfType 进行转换时则会自动进行类型筛选,遇到 int 类型的数据将不会转换,所以进行转换时 OfType 是首选
巨人的肩膀