注册表技术
- 注册表是一个庞大的数据库系统,它记录了用户安装在计算机上的软件、硬件信息和每一个程序的相互关系。注册表中存放着很多参数,直接控制整个系统的启动、硬件驱动程序的装载以及应用程序的运行
- Windows注册表包含Windows安装以及已安装软件和设备的所有配置信息。现在商用软件基本上都使用注册表来存储这些信息,COM组件必须把它的信息存储在注册表中,才能由客户程序调用。注册表的层次结构非常类似于文件系统,它记录了用户账号、服务器硬件以及应用程序的设置信息等。同INI文件相比,注册表可以控制的数据更多,而且不仅仅限于处理字符串类型的数据。注册表也包含了一些系统配置的信息,这些信息根据操作系统的不同而不同
- 选择“开始”→“运行”命令,在“打开”文本框中输入regedit,然后单击“确定”按钮打开“注册表编辑器”窗口
- .NET Framework提供了访问注册表的类,比较常用的是Registry类和RegistryKey类,这两个类都在Microsoft.Win32命名空间中
- 由于Windows 10以上系统本身的安全性问题,使用C#操作注册表时,可能会提示无法操作相应的注册表项,这时只需要为提示的注册表项添加everyone用户的读写权限即可
- egistry类不能被实例化,它的作用只是实例化RegistryKey类,以便开始在注册表中浏览。Registry类是通过静态属性来提供这些实例的,这些属性共有7个
- RegistryKey实例表示一个注册表项,这个类的方法可以浏览子键、创建新键、读取或修改键中的值。也就是说,该类可以完成对注册表项的所有操作。除了设置键的安全级别之外,RegistryKey类可以用于完成对注册表的所有操作
- 读取注册表信息主要通过RegistryKey类的OpenSubKey()、GetSubKeyNames()和GetValueNames()方法实现
OpenSubKey()方法用于检索指定的子项;如果要打开的项不存在,OpenSubKey()方法将返回null引用,而不是引发异常。
RegistryKey OpenSubKey(string name )
name:以只读方式打开的子项名称或路径。
返回值:请求的子项。如果操作失败,则为空引用。
如:使用OpenSubKey(string name )方法打开HKEY_LOCAL_MACHINE\SOFTWARE子键 - GetSubKeyNames()方法用于检索包含所有子项名称的字符串数组。其返回值包含当前项的子项名称的字符串数组。如果当前项已被删除,或是用户没有读取该项的权限,将触发异常
string[] GetSubKeyNames()
如:通过GetSubKeyNames()方法检索HKEY_LOCAL_MACHINE\SOFTWARE子键下包含的所有子项名称的字符串数组
GetValueNames()方法用于检索包含与此项关联的所有值名称的字符串数组。其返回值包含当前项的值名称的字符串数组。如果没有找到此项的值名称,则返回一个空数组;如果在注册表项设置了一个具有默认值的名称为空字符串的项,则GetValueNames()方法返回的数组中包含该空字符串
-
通过RegistryKey类的CreateSubKey()方法和SetValue()方法可以创建注册表信息
Register CreateSubKey(string subkey )方法用于创建一个新子项,或打开一个现有子项以进行写访问
subkey:待创建或打开的子项名称或路径。
返回值:RegistryKey对象,表示新建的子项或空引用。如果为subkey指定了零长度字符串,则返回当前的RegistryKey对象。
Register SetValue(string name,object value)方法用于设置注册表项中的名称/值对的值,
name:待存储的值的名称。
value:待存储的数据。
SetValue()方法用于从非托管代码中访问托管类,不应从托管代码调用。
-
由于注册表信息十分重要,所以一般不要对其进行写操作。因此在.Net Framework中并没有提供修改注册表键值的方法,而只是提供了一个危害性相对较小的SetValue()方法。通过这个方法,可以修改键值。在使用SetValue()方法时,如果检测到指定的键值不存在,就会创建一个新的键值
-
删除注册表中的信息主要通过RegistryKey类中的DeleteSubKey()方法、DeleteSubKeyTree()方法和DeleteValue()方法来实现
void DeleteSubKey(string subkey,bool throwOnMissingSubKey)方法用于删除不包含任何子键的子键
subkey:待删除的子项名称。
throwOnMissingSubKey:其值为true时,程序调用中待删除子键不存在,将产生一个错误信息;其值为false时,程序调用待删除子键不存在,不产生错误信息,依然正确运行。
如果删除的项有子级子项,将触发异常。必须将子项删除后,才能删除该项void DeleteSubKeyTree(string subkey)方法用于彻底删除指定的子键目录,包括删除该子键以及该子键以下的全部子键。由于此方法的破坏性非常强,所以在使用时要特别谨慎
subkey表示要彻底删除的子键名称。当删除的项为null时,则触发异常void DeleteValue(string name)方法主要用于删除指定的键值
name表示待删除的键值名称
如果在找不到指定值的情况下使用该值,又不想引发异常,可以使用DeleteValue(string name,bool throwOnMissingValue)重载方法。如果throwOnMissingValue参数为true,则不引发异常
网络编程
- 传输控制协议(transmission control protocol,TCP):TCP是一种以固接连线为基础的协议,可提供两台计算机间可靠的数据传送。TCP可以保证从一端将数据传送至连接的另一端时,数据能够确实送达,而且送达数据的排列顺序和送出时的顺序相同。TCP适合可靠性要求比较高的场合
- 与用户数据报协议(user datagram protocol,UDP):UDP是无连接通信协议,不保证可靠的数据传输,但能够向若干个目标发送数据,接收发自若干个源的数据。UDP以独立发送数据包的方式进行
- 一些防火墙和路由器会设置成不允许UDP数据包传输,因此若遇到UDP连接方面的问题,应先确定是否允许UDP
- 一台计算机只有单一的连到网络的“物理连接”(physical connection),所有的数据都通过此连接对内、对外送达特定的计算机,这就是端口。网络程序设计中的端口(port)并非真实的物理存在,而是一个假想的连接装置。端口被规定为一个在0~65535的整数,而HTTP服务一般使用80端口,FTP服务使用21端口。假如一台计算机提供了HTTP、FTP等多种服务,则客户机将通过不同的端口来确定连接到服务器的哪项服务上
- 网络程序中的套接字(socket)用于将程序与网络连接起来。套接字是一个假想的连接装置,就像用于连接电器与电线的插座
- 0~1023的端口号通常用于一些比较知名的网络服务和应用,普通网络应用程序则应该使用1024以上的端口号,以避免该端口号被另一个应用或系统服务所用。
- IP地址是每个计算机在网络中的唯一标识,它是32位或128位的无符号数字,使用4组数字表示一个固定的编号,如192.168.128.255就是局域网络中的编号。IP是一种低级协议,TCP和UDP都是在它的基础上构建的
- 3个与IP地址相关的类,分别是Dns类、IPAddress类和IPHostEntry类,它们都位于System.Net命名空间中
- Dns类是一个静态类,它从因特网域名系统(DNS)检索关于特定主机的信息,在IPHostEntry类的实例中返回来自DNS查询的主机信息。如果指定的主机在DNS中有多个入口,则IPHostEntry包含多个IP地址和别名
- IPAddress类包含计算机在IP网络上的地址,主要用来提供IP地址
- IPHostEntry类用来为因特网主机地址信息提供容器类,IPHostEntry类通常和Dns类一起使用
如果想在没有连网的情况下访问本地主机,可以使用本地回送地址127.0.0.1
- TCP程序设计是指利用Socket类、TcpClient类和TcpListener类编写的网络通信程序,这3个类都位于System.Net.Sockets命名空间中,利用TCP进行通信的两个应用程序是有主次之分的,一个称为服务器端程序,另一个称为客户端程序
- Socket类为网络通信提供了一套丰富的方法和属性,主要用于管理连接,实现Berkeley通信端套接字接口,同时它还定义了绑定、连接网络端点及传输数据所需的各种方法,提供处理端点连接传输等细节所需要的功能。TcpClient和UdpClinet等类在内部使用该类
- Socket类的方法和属性
- TcpClient类用于在同步阻止模式下通过网络来连接、发送和接收流数据。为了使TcpClient连接并交换数据,TcpListener实例或Socket实例必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接该侦听器
创建一个TcpClient,并调用Connect()方法连接。
使用远程主机的主机名和端口号创建TcpClient,此构造函数将自动尝试一个连接。
-TcpListener类用于在阻止同步模式下侦听和接受传入的连接请求。可使用TcpClient类或Socket类来连接TcpListener,并且可以使用IPEndPoint、本地IP地址及端口号或者仅使用端口号来创建TcpListener实例对象
- TcpClient类的常用属性、方法及说明
- TcpListener类的常用属性、方法及说明
-
UDP是网络信息传输的另一种形式。UDP通信和TCP通信不同,基于UDP的信息传递更快,但不提供可靠的保证。使用UDP传递数据时,用户无法知道数据能否正确地到达主机,也不能确定到达目的地的顺序是否和发送的顺序相同。虽然UDP是一种不可靠的协议,但如果需要较快地传输信息,并能容忍小的错误,可以考虑使用UDP。基于UDP通信的基本模式如下。
将数据打包(称为数据包),然后将数据包发往目的地。
接收别人发来的数据包,然后查看数据包。 -
UdpClient类用于在阻止同步模式下发送和接收无连接UDP数据报。因为UDP是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接,但可以选择使用下面两种方法之一来建立默认远程主机。
使用远程主机名和端口号作为参数创建UdpClient类的实例。
创建UdpClient类的实例,然后调用Connect()方法。
UdpClient类的常用属性、方法及说明
线程 -
每个正在操作系统上运行的应用程序都是一个进程,一个进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。每个线程都维护异常处理程序、调度优先级和一组系统用于在调度该线程前保存线程上下文的结构。线程上下文包括为使线程在线程的宿主进程地址空间中无缝地继续执行所需的所有信息,包括线程的CPU寄存器组和堆栈
-
单线程就是只有一个线程。默认情况下,系统为应用程序分配一个主线程,该线程执行程序中以Main()方法开始和结束的代码
-
使用Thread类对线程进行创建、暂停、恢复、休眠、终止及设置优先权等操作。另外,还可以通过Monitor类、Mutex类和lock关键字控制线程间的同步执行
-
Thread类位于System.Threading命名空间下,System.Threading命名空间提供一些可以进行多线程编程的类和接口。除同步线程活动和访问数据的类(Monitor、Mutex、Interlocked和AutoResetEvent等)外,该命名空间还包含一个ThreadPool类(它允许用户使用系统提供的线程池)和一个Timer类(它在线程池的线程上执行回调方法)
-
线程执行的程序代码由ThreadStart委托或ParameterizedThreadStart委托指定
-
线程运行期间,不同的时刻会表现为不同的状态,但它总是处于由ThreadState定义的一个或多个状态中。用户可以通过使用ThreadPriority枚举为线程定义优先级,但不能保证操作系统会接受该优先级
-
-
创建新线程时,需要使用Thread类.Thread类具有接受一个ThreadStart委托或ParameterizedThreadStart委托的构造函数.该委托包装了调用Start()方法时由新线程调用的方法。创建了Thread类的对象之后,线程对象已存在并已配置,但并未创建实际的线程,这时,只有在调用Start()方法后,才会创建实际的线程
-
void Start([object parameter])方法用来使线程被安排执行
parameter为一个对象,包含线程执行的方法要使用的数据
如果线程已经终止,就无法通过再次调用Start()方法重新启动。
-
线程的生命周期
-
Suspend()方法用来挂起线程,如果线程已挂起,则不再起作用。调用Suspend()方法挂起线程时,.NET通常会允许该线程再执行几个指令,以达到线程可以安全挂起的状态
-
Resume()方法用来继续已挂起的线程。通过Resume()方法恢复被暂停的线程时,无论调用了多少次Suspend()方法,调用Resume()方法均会使另一个线程脱离挂起状态,并导致该线程继续执行
-
Thread类的Sleep()方法用来挂起当前线程,即让线程休眠指定的时间
Sleep(int millisecondsTimeout) 将当前线程挂起指定的时间
millisecondsTimeout表示线程被阻止的毫秒数。其值为0,表示应挂起线程,使其他等待线程执行;其值为Infinite,表示要无限期阻止线程
Sleep(timespan timeout) 将当前线程阻止指定的时间
timeout表示线程被阻止时间量的TimeSpan。其值为0,表示应挂起此线程,使其他等待线程执行;其值为Infinite,表示要无限期阻止线程 -
线程的Abort()方法用于永久地停止托管线程。调用Abort()方法时,公共语言运行库在目标线程中引发ThreadAbortException异常,目标线程可捕捉此异常。一旦线程被终止,它将无法再重新启动
Abort()终止线程,在调用此方法的线程上引发ThreadAbortException异常,以开始终止此线程的过程
Abort(object stateInfo)终止线程,在调用此方法的线程上引发ThreadAbortException异常,以开始终止此线程并提供有关线程终止的异常信息的过程.stateInfo对象包含了应用程序特定的信息(如状态),该信息可供正被终止的线程使用 -
Join()方法用来阻止调用线程,直到某个线程终止时为止。它有3种重载形式
join():在继续执行标准的COM和SendMessage消息处理期间阻止调用线程,直到某个线程终止为止
join (int millisecondsTimeout)在继续执行标准的COM和SendMessage消息处理期间阻止调用线程,直到某个线程终止或经过了指定时间为止.millisecondsTimeout:等待线程终止的毫秒数
join (timespan timeout)在继续执行标准的COM和SendMessage消息处理期间阻止调用线程,直到某个线程终止或经过了指定时间为止.timeout:等待线程终止的时间量的TimeSpan -
如果在应用程序中使用了多线程,辅助线程还没有执行完毕,在关闭窗体时必须关闭辅助线程,否则会引发异常
-
线程的优先级指定一个线程相对于另一个线程的优先级。每个线程都有一个分配的优先级。在公共语言运行库内创建的线程最初被分配为Normal优先级,而在公共语言运行库外创建的线程,在进入公共语言运行库时将保留其先前的优先级
-
线程是根据其优先级而调度执行的,用于确定线程执行顺序的调度算法随操作系统的不同而不同
-
线程的优先级不会影响线程的状态,线程状态在操作系统调度该它之前必须为Running
-
开发人员可以通过访问线程的Priority属性来获取和设置其优先级。Priority属性用来获取或设置一个值,该值指示线程的调度优先级
- 线程同步是指并发线程高效、有序地访问共享资源所采用的技术,所谓同步,是指某一时刻只有一个线程可以访问资源,只有当资源所有者主动放弃了代码或资源的所有权,其他线程才可以使用这些资源
- 线程同步可以分别使用C#中的lock关键字、Monitor类和Mutex类实现
lock关键字可以用来确保代码块完成运行,而不会被其他线程中断,它是通过在代码块运行期间为给定对象获取互斥锁来实现的
lock语句以关键字lock开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能由一个线程执行的代码块
提供给lock语句的参数必须为基于引用类型的对象,该对象用来定义锁的范围。严格地说,提供给lock语句的参数只是用来唯一标识由多个线程共享的资源,所以它可以是任意类实例。然而,此参数通常表示需要进行线程同步的资源.最好避免锁定public类型或不受应用程序控制的对象实例
- Monitor类提供了同步对象的访问机制,它通过向单个线程授予对象锁来控制对对象的访问,对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象锁时,其他任何线程都不能获取该锁。Monitor类的主要功能如下。
根据需要与某个对象相关联。
是未绑定的,可以直接从任何上下文调用它。
不能创建Monitor类的实例。
使用Monitor类锁定的是对象(即引用类型)而不是值类型
- 使用lock关键字更简单一些,那为何还要使用Monitor类呢?这是因为Monitor类有更好的控制能力。例如,它可以使用Wait()方法指示活动的线程等待一段时间,当线程完成操作时,还可以使用Pulse()方法或PulseAll()方法通知等待中的线程
- Mutex类与Monitor类类似,它防止多个线程在某一时间同时执行某个代码块,Mutex类可以用来使跨进程的线程同步
- 使用WaitHandle.WaitOne()方法请求互斥体的所属权,拥有互斥体的线程可以在对WaitOne()方法的重复调用中请求相同的互斥体而不会阻止其执行,但线程必须调用同样多次数的ReleaseMutex()方法以释放互斥体的所属权。Mutex类强制线程标识,因此互斥体只能由获得它的线程释放
- 使用Mutex类实现线程同步很简单,首先实例化一个Mutex类对象,其构造函数中比较常用的有public Mutex(bool initallyOwned)。其中,参数initallyOwned指定了创建该对象的线程是否希望立即获得其所有权,当在一个资源得到保护的类中创建Mutex类对象时,常将该参数设置为false。然后在需要单线程访问的地方调用其等待方法,等待方法请求Mutex对象的所有权。这时,如果该所有权被另一个线程所拥有,则阻塞请求线程,并将其放入等待队列中,请求线程将保持阻塞,直到Mutex对象收到了其所有者线程发出将其释放的信号为止。所有者线程在终止时释放Mutex对象,或者调用ReleaseMutex()方法来释放Mutex对象。
- async/await是从.NET 5.0开始引入的关键字,其中,async关键字用于声明异步方法,而await关键字则用于调用异步方法,它们使得.NET中的异步编程变得更加简单
- async和await的基本使用规则如下。
使用async修饰的方法为异步方法,但需要配合await使用,否则就是普通的方法。
当async方法执行遇到await时,立即将控制权转移到async方法的调用者。
由调用者决定是否需要等待async方法执行完再继续往下执行。
await会挂起当前方法,即阻塞当前方法继续往下执行,转交控制权给调用者。
一个方法中如果有await调用,则该方法也必须被修饰为async。
调用泛型方法时,通常在方法前加await关键字,这样方法调用的返回值就是泛型指定的T类型的值 - Task类表示一个异步操作,它位于System.Threading.Tasks命名空间下,它是.NET Framework 4.0中首次引入的基于任务的异步模式的核心组件之一,对于有返回值的操作,需要使用Task类
- - 异步方法主要有以下特点。
方法名通常以Async结尾,但不是强制。
返回值一般是Task类型,其中的T是真正的返回值类型。
如果没有返回值,也建议将返回值声明为非泛型的Task。
调用异步方法时,一定要使用await关键字,否则,异步方法将作为同步方法运行
await关键字还有一个作用,就是对于返回值为Task类型的异步方法,经过await调用,可以自动将返回数据从Task中提取出来,从而可以直接使用相应类型的变量去接收该值
实际开发中,遇到以Async结尾的方法,应先查看其返回值是否为Task或者Task。如果是,则应该使用await关键字调用它,不能使用非Async结尾的同名方法