自制小笔记_C#后期相关问题解决方案

*C#是一种功能强大、易于学习的编程语言,具有以下几个优势和好处:**

  1. 简单易学:C#被设计为一种面向对象的编程语言,其语法类似于Java和C++,但更加简单易学,使得初学者可以快速入门。

  2. 跨平台性能强:C#代码可以在各种平台上运行,包括Windows、Linux、iOS和Android等。此外,C#被编译成中间语言IL(Intermediate Language),可由CLR(Common Language Runtime)解释执行,因此具有良好的性能和可扩展性。

  3. 丰富的框架库:C#内置了大量的类库和框架,可以方便地完成各种任务,如数据处理、网络编程、图形界面开发等。同时,它还支持多种第三方库和框架,如Entity Framework、ASP.NET MVC等,可以加快开发进度和提高效率。

  4. 安全性高:C#具有严格的类型检查和内存管理机制,能够有效避免常见的安全漏洞和错误,从而提高系统的稳定性和可靠性。

  5. 支持多种编程范式:除了面向对象编程,C#还支持函数式编程、泛型编程、异步编程等多种编程范式,使得开发人员可以选择最适合自己的方式进行编程。

总之,C#是一种优秀的编程语言,在开发桌面应用、Web应用和移动应用等方面都有广泛的应用。它不仅易于学习使用,而且具有很好的跨平台性能和安全性,是现代软件开发中不可或缺的一部分。

类与方法

疑难杂症解决方案:

C#无边框控制窗体移动

虽然C#自带有边框,但是在开发过程中那个边框并不是很好看,当我们不使用那个边框的时候,那么就会面临一个问题,没有C#自带的边框了,那么窗体怎么进行移动呢?

设置FormBorderStyle为None,则 窗体是 无法被拖动的。

想要拖动移动窗口,

这涉及到C#的三个事件:

1.MouseDown : 鼠标单击事件

2.MouseMove : 鼠标经过事件

3.MouseUp : 鼠标抬起事件

在写的时候依然是这三个顺序:

首先设置两个全局变量:

Point mouseOff;//用于获取鼠标位置 bool leftFlag;//移动标识

然后写第一个事件:

private void MainForm_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)//判断是否单击的是左键
            {
                mouseOff = new Point(e.X,e.Y);//获取当前鼠标位置
                leftFlag = true;//用于标记窗体是否能移动(此时鼠标按下如果说用户拖动鼠标则窗体移动)
            }
        }

第二个事件:

private void MainForm_MouseMove(object sender, MouseEventArgs e)
        {
            if (leftFlag)
            {
        //用当前鼠标的位置 - 之前获取的鼠标位置 = 用户移动窗体的距离 
        //Location: 设置或获取窗体左上角位置
                Location = new Point(Control.MousePosition.X - mouseOff.X, Control.MousePosition.Y - mouseOff.Y);
            }
        }

第三个事件:(此时移动已经完成 但是如果不把leftFlag标识一下,则会继续移动)

private void MainForm_MouseUp(object sender, MouseEventArgs e)
        {
            if (leftFlag)
            {
                leftFlag = false; //释放鼠标标识为false 表示窗体不可移动
            }
        }

通过以上代码可以完成 无边框窗体移动!

第二种方法

要想实现窗体的拖动,将如下代码放入到控件的MouseDown事件中即可:

 //调用API函数  添加引用using System.Runtime.InteropServices;
    [DllImport("user32.dll")]
    public static extern bool ReleaseCapture();
 
    [DllImport("user32.dll")]
    public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
 
    private void FrmMain_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Clicks == 1)
        {
            //窗体移动
            if (e.Button == MouseButtons.Left)
            {
                ReleaseCapture(); //释放鼠标捕捉
                //发送左键点击的消息至该窗体(标题栏)
                SendMessage(this.Handle, 0xA1, 0x02, 0);
            }
        }
    }

对于最小化、最大化、关闭的实现代码如下:

 this.WindowState = FormWindowState.Maximized;  // 最大化
 this.WindowState = FormWindowState.Minimized;  // 最小化
 this.WindowState = FormWindowState.Normal;      // 一般状态
 this.Close();  // 关闭
​
//最大化优化图标
 //this.WindowState = FormWindowState.Maximized;  // 最大化
            if (this.WindowState == FormWindowState.Maximized)
            {
                this.WindowState = FormWindowState.Normal;
                Image backImage = Resources.最大化;//这里图片调用的时资源库中添加好的图片  需要添加引用 using CalibrationAndMatching.Properties;
                but_max.BackgroundImage = backImage;
                LogClass.ShowMsg("主窗体还原");
​
            }
            else
            {
                this.WindowState = FormWindowState.Maximized;
                Image backImage = Resources.还原;//图片
                but_max.BackgroundImage = backImage;
​
                LogClass.ShowMsg("主窗体最大化");
            }

C#_让WinForm窗体拥有动画效果(淡入淡出等…)

// <summary>
/// 窗体动画函数
/// </summary>
/// <param name="hwnd">指定产生动画的窗口的句柄</param>
/// <param name="dwTime">指定动画持续的时间</param>
/// <param name="dwFlags">指定动画类型,可以是一个或多个标志的组合。</param>
/// <returns></returns>
[DllImport("user32")]
private static extern bool AnimateWindow(IntPtr hwnd, int dwTime, int dwFlags);
 
//下面是可用的常量,根据不同的动画效果声明自己需要的
private const int AW_HOR_POSITIVE = 0x0001;//自左向右显示窗口,该标志可以在滚动动画和滑动动画中使用。使用AW_CENTER标志时忽略该标志
private const int AW_HOR_NEGATIVE = 0x0002;//自右向左显示窗口,该标志可以在滚动动画和滑动动画中使用。使用AW_CENTER标志时忽略该标志
private const int AW_VER_POSITIVE = 0x0004;//自顶向下显示窗口,该标志可以在滚动动画和滑动动画中使用。使用AW_CENTER标志时忽略该标志
private const int AW_VER_NEGATIVE = 0x0008;//自下向上显示窗口,该标志可以在滚动动画和滑动动画中使用。使用AW_CENTER标志时忽略该标志该标志
private const int AW_CENTER = 0x0010;//若使用了AW_HIDE标志,则使窗口向内重叠;否则向外扩展
private const int AW_HIDE = 0x10000;//隐藏窗口
private const int AW_ACTIVE = 0x20000;//激活窗口,在使用了AW_HIDE标志后不要使用这个标志
private const int AW_SLIDE = 0x40000;//使用滑动类型动画效果,默认为滚动动画类型,当使用AW_CENTER标志时,这个标志就被忽略
private const int AW_BLEND = 0x80000;//使用淡入淡出效果
窗体代码(将窗体的FormBorderStyle属性设置为none):
​
private void Form1_Load(object sender, EventArgs e)
{
   int x = Screen.PrimaryScreen.WorkingArea.Right - this.Width;
   int y = Screen.PrimaryScreen.WorkingArea.Bottom - this.Height;
   this.Location = new Point(x, y);//设置窗体在屏幕右下角显示
   AnimateWindow(this.Handle, 1000, AW_SLIDE | AW_ACTIVE | AW_VER_NEGATIVE);
}
​
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
   AnimateWindow(this.Handle, 1000, AW_BLEND | AW_HIDE);
}
​

C#选择文件夹并设置默认路径

System.Windows.Forms.FolderBrowserDialog folderBrowserDialog = new System.Windows.Forms.FolderBrowserDialog();
            //folderBrowserDialog.Description = "选择Word文档生成的文件夹";
            //folderBrowserDialog.ShowNewFolderButton = false;
            //folderBrowserDialog.RootFolder = Environment.SpecialFolder.Personal;
            folderBrowserDialog.SelectedPath = "{默认路径}";
            folderBrowserDialog.ShowDialog();
            if (folderBrowserDialog.SelectedPath == string.Empty)
            {
                MessageBox.Show("未选择文件夹");
                return;
            }
            this.txt1.Text = folderBrowserDialog.SelectedPath;

C#通过文件路径获取文件名

string fullPath = @"\WebSite1\Default.aspx";
string filename = System.IO.Path.GetFileName(fullPath);//文件名 “Default.aspx”
string extension = System.IO.Path.GetExtension(fullPath);//扩展名 “.aspx”
string fileNameWithoutExtension = System.IO.Path.GetFileNameWithoutExtension(fullPath);// 没有扩展名的文件名 “Default”
​
//获取选中的文件路径
public static string LoadZipFile(string sPath)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Multiselect = true;
fileDialog.Title = “请选择文件”;
fileDialog.Filter = “所有文件(*.tek)|*.*";//fileDialog.FileName = "*.”;
//fileDialog.InitialDirectory = Path.GetDirectoryName(CGlobal.systemConfigParamEnter.configSetSourceName);
if (fileDialog.ShowDialog() != DialogResult.OK)
{
return sPath;
}
else
{
sPath = fileDialog.FileName;
return sPath;
}
}

c#判断MySQL数据库中是否存在某个数据库或数据表的方法

一、判断某数据库是否存在

(一)单纯判断数据库dbname是否存在

SQL语句为:

SELECT * FROM information_schema.SCHEMATA where SCHEMA_NAME='dbname';

c#实现代码如下:

String sqlConn = "server=10.0.0.3;port=3306;user=root;password=mypassword;";
MySqlConnection conn = new MySqlConnection(sqlConn);//
string sqlDB = "SELECT * FROM information_schema.SCHEMATA where SCHEMA_NAME='dbname';";
MySqlDataAdapter adp = new MySqlDataAdapter(sqlDB, conn);
DataSet ds = new DataSet();
adp.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
   MessageBox.Show("数据库已存在");
else
   MessageBox.Show("数据库不存在!");

(二)如果判断数据库dbname存在,则删除

SQL语句如下:

DROP DATABASE IF EXISTS `test1`;

MySqlCommand cmd=new MySqlCommand(string.Format("DROP DATABASE IF EXISTS 'test';"));
cmd.ExecuteNonQuery();

(三)如果判断数据库dbname不存在,则创建数据库

SQL语句如下:

CREATE DATABASE IF NOT EXISTS `test1` ;

c#执行代码如下:

MySqlCommand cmd=new MySqlCommand(string.Format("CREATE DATABASE IF NOT EXISTS 'test';"));
cmd.ExecuteNonQuery();

二、判断数据库中某数据表是否存在

(一)单纯判断dbname数据库中是否存在tbname表格

SQL语句为:

SELECT * FROM information_schema.TABLES where table_name='tbname' and TABLE_SCHEMA='dbname';

c#实现代码如下:

String sqlConn = "server=10.0.0.3;port=3306;user=root;password=mypassword;";
MySqlConnection conn = new MySqlConnection(sqlConn);//
string sqlTB = "SELECT * FROM information_schema.TABLES where table_name='tbname' and TABLE_SCHEMA ='dbname';";
MySqlDataAdapter adp = new MySqlDataAdapter(sqlTB, conn);
DataSet ds = new DataSet();
adp.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
   MessageBox.Show("数据库表已存在");
else
   MessageBox.Show("数据库表不存在!");

(二)如果判断dbname数据库中不存在tbname数据表,则创建

SQL语句为:

CREATE TABLE IF NO NOT EXISTS `tbname` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `unit` varchar(45) DEFAULT NULL,
  `name` varchar(45) DEFAULT NULL,
  `phone` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=56370 DEFAULT CHARSET=utf8;

C#实现代码如下:

MySqlCommand cmd;
cmd=new MySqlCommand(string.Format("CREATE DATABASE IF NOT EXISTS dbname;"),conn);
cmd.ExecuteNonQuery();
string sqlcreatetable = string.Format("CREATE TABLE IF NOT EXISTS dbname.tbname(id int(11) NOT NULL AUTO_INCREMENT,unit varchar(45) DEFAULT NULL,name varchar(45) DEFAULT NULL,phone varchar(45) DEFAULT NULL,PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=56370 DEFAULT CHARSET=utf8;");
cmd = new MySqlCommand(sqlcreatetable, conn);
cmd.ExecuteNonQuery();

(三)如果判断dbname数据库中存在tbname数据表,则删除

SQL语句为:

Drop table if exists dbname.tbname;

c#实现代码如下:

MySqlCommand cmd;
cmd=new MySqlCommand(string.Format("Drop table if exists dbname.tbname;"),conn);
cmd.ExecuteNonQuery();

c#数组的赋值与拷贝

int[] array = new int[10];int[] copy = new int[array.Length];Array.Copy(array, copy, array.Length);

需要深入研究的可以参考链接 https://www.cnblogs.com/chihirosan/p/5163638.html,有函数拷贝的效率比较.

两个效率较快的函数:

1.Array.Copy的方法,原生的数组复制方法【没有了Copy,可以处理任意值类型】,处理2000000*100字节620.042ms,

public static T2[] Arr2Arr3<T1, T2>(T1[] from)    where T1 : struct    where T2 : struct{    int byteNum = from.Length * from[0].Sizeof();    T2 testByte = new T2();    T2[] dTo = new T2[byteNum / testByte.Sizeof()];     Array.Copy(from, dTo, dTo.Length);    return dTo;}

2.通过Buffer.BlockCopy拷贝数组,速度最快,感觉类似于c++的memcpy【没有了Copy,可以处理任意值类型】,处理2000000*100字节300.0329ms.

public static T2[] Arr2Arr4<T1, T2>(T1[] from)    where T1 : struct    where T2 : struct{    int byteNum = from.Length * from[0].Sizeof();    T2 testByte = new T2();    T2[] dTo = new T2[byteNum / testByte.Sizeof()];     Buffer.BlockCopy(from, 0, dTo, 0, byteNum);     return dTo;}

上面两个函数Arr2Arr3与Arr2Arr4为泛型约束代码,目前不清楚如何调用,这里仅记下Array.Copy函数与Buffer.BlockCopy两个函数.

下面为测试程序伪代码,可以用Array.Copy函数与Buffer.BlockCopy函数分别测试.

byte[] from = new byte[100];from[0] = 1;from[1] = 1; var last = DateTime.Now;for (int i = 0; i < 2000000; i++){    。。。}Console.WriteLine((DateTime.Now- last).TotalMilliseconds);

一些偶尔碰到的知识

extern是干什么用的?

C# extern 在方法声明中使用extern修饰符支持在外部实现方法。 C# extern 在方法声明中使用extern修饰符支持在外部实现方法。外部修饰符的常见方法是在使用Interop 服务调入非托管代码时与 DllImport 属性一起使用;在这种情况下,该方法还必须声明为 static,如下面的示例所示:

例如:

[DllImport("avifil32.dll")] private static extern void AVIFileInit();

用了extern,就表示这个变量是全局的和动态的,不存在使用哪一个的问题.比如3号文件的int A变成289了,那么所有其它4个文件的A都会同时变成289.如果后来5号文件的A变成78,那其它所有4个文件的A也会同时变成78.所以extern是动态的而且是全局的. 虽然定义这么多A,但所有的文件同时使用一个A.只要指定extern A,不管你在5个文件的哪一个文件里指定效果都一样.

C#获取本机IP地址的4种方式

1.使用 Dns.GetHostEntry() 函数获取本地 IP 地址

Dns 类获取与 Internet 上的主机有关的信息。在 C# 中,Dns 类中有很多方法可以实现 DNS 相关功能。其中 Dns.GetHostEntry() 函数用于获取主机的 IP 地址。 以下代码示例向我们展示了如何使用 C# 中的 Dns.GetHostEntry() 函数获取计算机的本地 IP 地址。

using System;
using System.Net;

namespace get_local_ip_address
{
    class Program
    {
        static void Main(string[] args)
        {
            IPHostEntry ipEntry = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in ipEntry.AddressList)
            {
                Console.WriteLine("IP Address: " + ip.ToString());
            }
        }
    }
}

// 输出
IP Address 0: fe80::1db8:23a7:6483:8976%20
IP Address 1: 192.168.43.239
123456789101112131415161718192021

在上面的代码中,我们将 Dns.GetHostName() 函数作为 Dns.GetHostEntry() 函数的参数传递,以获取 C# 中本地计算机的 IP 地址。这种方法的唯一问题是,它为我们提供了机器的所有 IP 地址。要获取特定的 IP 地址,我们必须使用 C# 编写以下代码。

using System;
using System.Net;

namespace get_local_ip_address
{
    class Program
    {
        static void Main(string[] args)
        {
            var host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    Console.WriteLine("IP Address = " + ip.ToString());
                }
            }
        }
    }
}

// 输出:
IP Address = 192.168.43.239
1234567891011121314151617181920212223

在上面的代码中,我们检查了 ip.AddressFamily 是否等于 AddressFamily.InterNetwork。此条件检查 ip 变量中的 IP 地址是否为 IPv4地址。上面的代码仅返回我们计算机的 IPv4 地址。如果要获取本机的 IPv6 地址,可以使用 AddressFamily.InterNetworkV6 属性,而不是 AddressFamily.InterNetwork 属性。

2.使用 Socket.LocalEndPoint 属性获取本地 IP 地址

当有多个 IP 地址时,获取我们机器的本地 IP 地址的一种更准确的方法是连接 UDP 套接字,然后读取其本地端点。C# 中的 Socket 类实现了 Berkeley 套接字接口,该接口提供了网络通信的功能。以下代码示例向我们展示了如何使用 Socket.LocalEndPoint 属性来确定我们的计算机在 C# 中的本地 IP 地址。

using System;
using System.Net;
using System.Net.Sockets;

namespace get_local_ip_address
{
    class Program
    {
        static void Main(string[] args)
        {
            string localIP = string.Empty;
            using (Socket socket = new Socket(AddressFamily.InterNetwork, 							SocketType.Dgram, 0))
            {
                socket.Connect("8.8.8.8", 65530);
                IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
                localIP = endPoint.Address.ToString();
            }
            Console.WriteLine("IP Address = " + localIP);
        }
    }
}

// 输出:
IP Address = 192.168.43.239
123456789101112131415161718192021222324

在上面的代码中,我们将 socket 套接字连接到我们的本地计算机,并使用 socket.LocalEndPoint 属性提取了 socket 的本地端点。我们使用 endPoint.Address 属性来获取 endPoint 的 IP 地址。

3.使用 Linq 获取本地 IP 地址

Linq 或语言集成查询提供了 C# 中的 SQL 功能。我们还可以使用 Linq 和 Dns.GetHostEntry() 函数在 C# 中获取我们计算机的本地 IP 地址。

以下代码示例向我们展示了如何使用 Linq 在 C# 中获取计算机的本地 IP 地址。

using System;
using System.Linq;
using System.Net;

namespace get_local_ip_address
{
    class Program
    {
        static void Main(string[] args)
        {
            if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
            {
                Console.WriteLine("No Network Available");
            }

            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());

            var ippaddress =  host
                .AddressList
                .FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
            Console.WriteLine(ippaddress);
        }
    }
}

// 输出:
192.168.43.239
123456789101112131415161718192021222324252627

在上面的代码中,我们使用 C# 中的 Linq 的 Dns.GetHostEntry() 函数确定了计算机的本地 IPv4 地址。其余代码类似于我们的第一个示例;唯一的区别是,我们从 Dns.GetHostEntry() 函数返回的地址列表中仅提取了一个 IPv4 地址。

4. 使用 NetworkInterface 类获取本地 IP 地址

NetworkInterface 类包含有关本地计算机上网络接口的数据并提供网络接口的统计信息和配置。NetworkInterface 类中的 GetAllNetworkInterfaces() 函数为我们提供了本地计算机上的所有网络接口。使用了 NetworkInterface 类中的 NetworkInterfaceType 属性获取网络接口的类型。

以下代码示例显示了我们如何使用所有这些功能来获取 C# 中本地计算机的本地 IP 地址。

using System;
using System.Linq;
using System.Net;

namespace get_local_ip_address
{
    class Program
    {
        public static void getLocalIPAddressWithNetworkInterface(NetworkInterfaceType _type)
        {
            string output = "";
            foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
            {
                if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up)
                {
                    foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
                    {
                        if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
                        {
                            output = ip.Address.ToString();
                        }
                    }
                }
            }
            Console.WriteLine("IP Address = " + output);
        }
        static void Main(string[] args)
        {
            getLocalIPAddressWithNetworkInterface(NetworkInterfaceType.Wireless80211);
        }
    }
}

// 输出:
IP Address = 192.168.43.239
1234567891011121314151617181920212223242526272829303132333435

在上面的代码中,我们使用 C# 中的 NetworkInterface 类检索了本地计算机的 WLAN IPv4 地址。上面的代码可用于获取我们本地计算机上的任何类型的 IP 地址。例如,如果要获取以太网接口的 IP 地址,则必须在函数调用中通过传递 NetworkInterfaceType.Ethernet 来指定它。

C# TimeSpan 的 Days、TotalDays、Hours、TotalHours等区别

C#里两个时间相减,得到一个 TimeSpan 实例,TimeSpan 有一些属性:Days、TotalDays、Hours、TotalHours、Minutes、TotalMinutes、Seconds、TotalSeconds、Ticks,注意没有 TotalTicks。

这些属性名称开始理解有些困难,但阅读本文后,相应您一定茅塞顿开。

举例说明

  • 时间 1 是 2010-1-2 8:43:35;

  • 时间 2 是 2010-1-12 8:43:34。

用时间 2 减时间 1,得到一个 TimeSpan 实例。

那么时间 2 比时间 1 多 9 天 23 小时 59 分 59 秒。

那么,Days 就是 9,Hours 就是 23,Minutes 就是 59,Seconds 就是 59。

再来看 Ticks,Tick 是一个计时周期,表示一百纳秒,即一千万分之一秒,那么 Ticks 在这里表示总共相差多少个时间周期,即:9 * 24 * 3600 * 10000000 + 23 * 3600 * 10000000 + 59 * 60 * 10000000 + 59 * 10000000 = 8639990000000。3600 是一小时的秒数。

TotalDays 就是把 Ticks 换算成日数,即:8639990000000 / (10000000 * 24 * 3600) = 9.99998842592593。

TotalHours 就是把 Ticks 换算成小时数,即:8639990000000 / (10000000 * 3600) = 239.999722222222。

TotalMinutes 就是把 Ticks 换算成分钟数,即:8639990000000 / (10000000 * 60) = 14399.9833333333。

TotalSeconds 就是把 Ticks 换算成秒数,即:8639990000000 / (10000000) = 863999。

负数

上面是较晚的日期减较早的日期,所以各属性值为正数,如果是较早的日期减较晚的日期,则属性值为负数。

文件夹**类/文件 流/IO

Encoding 表示字符编码。

Encoding.UTF8.GetString(arr);

1、Directory 操作文件夹//类

//方法:

CreateDirectory 创建文件夹 Delete 删除文件夹 Move 剪切文件夹 Exists 判断是否存在 GetFiles 获得指定的目录下所有文件的全路径 GetDirectory 获得指定目录下所有文件夹的全路径

创建文件夹CreateDirectory

Directory.CreateDirectory(@"路径");

删除文件夹Delete

Directory.Delete(@"路径");

剪切文件夹Move

Directory.Move(@"路径");

获取指定文件夹下所有文件路径GetFiles

string[] path=Directory.GetFiles (@"路径");

获得指定目录下所有文件夹的全路径GetDirectory

string[] path=Directory.GetDirectory  (@"路径");

判断是否存在Exist

if(Directory.Exists(@"路径"))
{
        
}

2,FileStream 类字节流

参数说明如下:

(1)FileStream() 作用:创建FileStream对象,参数:第一个是路径,第二个是文件模式FileMode枚举,第三个数据模式FileAcess

初始化FileStream时使用包含文件共享属性(System.IO.FileShare)的构造函数比使用自定义线程锁更为安全和高效

(2)FileMode(以何种方式打开或者创建文件):CreateNew(创建新文件)、Create(创建并覆盖)、Open(打开)、OpenOrCreate(打开并创建)、Truncate(覆盖文件)、Append(追加);

(3)FileAccess(文件流对象如何访问该文件):Read(只读) 、Write(写)、ReadWirte(读写);

(4)FileShare(进程如何共享文件):None(拒绝共享)、Read 、Write、ReadWrite(同时读写)、Delete;

(5)bufferSize(缓冲区大小设置)

将一个文件复制到另外一个文件

try
            {
                using (FileStream w1 = new FileStream(@"D:\实验路径\新建文本文档.txt", FileMode.Create, FileAccess.Write))//写入
                {
                    using (FileStream f1 = new FileStream(@"D:\实验路径\新建文本文档2.txt", FileMode.Open, FileAccess.Read))//读取
                    {
                        byte[] arr = new byte[64];
                        int count = 0;
                        while ((count = f1.Read(arr, 0, arr.Length)) != 0)
                        {
                            w1.Write(arr, 0, count);
                        }
                    }
                }
            }
            catch (Exception e)
            {

                Console.WriteLine(e);
            }

读取

public void FSRead(string path)
        {
            using (FileStream fsread = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read))
            {
                //字节流一段一段读取

                byte[] buffer = new byte[1024 * 1024 * 3];//设置最大一次读3m
                //返回本次实际读取到的有效字节数
                int count = 0;
                while ((count = fsread.Read(buffer, 0, buffer.Length)) != 0)
                {
                    //将字节数组中每一个元素按照指定的编码格式解码成字符串
                    textBox2.AppendText(Encoding.UTF8.GetString(buffer, 0, count));                
                    //textBox2.AppendText();
                }
                //关闭流
                //fsread.Close();
                //释放流所占用的资源
                //fsread.Dispose();
            }
        }

写入

  using (FileStream fsWrite = new FileStream(path, FileMode.Create, FileAccess.Write))
                {
                    byte[] buffer = Encoding.UTF8.GetBytes(data);
                    fsWrite.Write(buffer, 0, buffer.Length);


                }

3,File类

File.Create( string path)

在指定路径中创建或覆盖一个文件(注意:如果指定路径下已经有此文件,那么原有的会被覆盖)

File.Create(@"d:\a.txt");

File.Delete( string path)

根据指定路径删除一个文件(注意:此删除是彻底删除,回收站也没有)

File.Delete(@"d:\a.txt");

File.Copy( string originPath, string newPath)**

根据指定路径复制一个文件;

参数一:要复制的文件路径

参数二:复制出来的新的文件路径及文件名字

注意:

1> 如果要复制的文件不存在,编辑器会报错;

2> 如果参数一和参数二路径及文件名一样,编辑器会报错;

3> 新复制出来的文件与原来文件大小、里边内容都是一样的。

File.Copy(@"d:\a.txt",@"e:\aa.txt");

File与FileStream的介绍

1.1 File类,是一个静态类,支持对文件的基本操作,包括创建,拷贝,移动,删除和打开一个文件。File类方法的参量很多时候都是路径path。主要提供有关文件的各种操作,在使用时需要引用System.IO命名空间。 1.2 FileStream文件流 只能处理原始字节(raw byte)。FileStream 类可以用于任何数据文件,而不仅仅是文本文件。FileStream 对象可以用于读取诸如图像和声音的文件,FileStream读取出来的是字节数组,然后通过编码转换将字节数组转换成字符串。

1.3 区别:file:是一个文件的类,对文件进行操作的;filestream:文件流.对txt,xml等文件写入内容的时候需要使用的一个工具.打个形象的比喻.file是笔记本,需要filestream的这个笔才能写.

2.1 File类创建Txt

string path = @"C:\Users\Administrator\Desktop\1.txt";
   File.Open(path, FileMode.OpenOrCreate);

2.2 File类Txt拼接内容

  List<string> users = new List<string>();
            users.Add("张三|1000");
            users.Add("李四|10000");
            users.Add("王五|5000");
            users.Add("赵六|6000");
             File.AppendAllLines(path, users, Encoding.Default);

2.3 File类字节形式读取txt文档

byte[] buttf = File.ReadAllBytes(path);
string str = Encoding.Default.GetString(buttf, 0, buttf.Length);

2.4 File类实现txt员工工资加倍

 string[] filestr = File.ReadAllLines(path, Encoding.Default);
   for (int i = 0; i < filestr.Length; i++)
     {
        string[] str = filestr[i].Split('|');
        filestr[i] = str[0] + "|" + (Convert.ToDouble(str[1]) * 2).ToString();
      }
     File.AppendAllLines(path, filestr, Encoding.Default);

3.文件流

3.1文件流文字形式读取

 byte[] bytsize = new byte[1024 * 1024 * 5];
            using (FileStream stream = new FileStream(path, FileMode.OpenOrCreate,FileAccess.ReadWrite))
            {
                while (true)
                {
                    int r = stream.Read(bytsize, 0, bytsize.Length);
                    //如果读取到的字节数为0,说明已到达文件结尾,则退出while循
                    if (r == 0)
                    {
                        break;
                    }
                           string str = Encoding.Default.GetString(bytsize, 0, r);
                Console.WriteLine(str);
            }
        }

3.2文件流文字形式写入

 string str = "今天天气好晴朗,处处好风光";
            byte[] buttf = Encoding.Default.GetBytes(str);
            //文件流的写入
            using (FileStream fscreat = new FileStream(path, FileMode.Append, FileAccess.Write))
            {
                 fscreat.Write(buttf, 0, buttf.Length);
        }

3.3文件流视频形式复制

 byte[] bytsize = new byte[1024 * 1024 * 5];       
string videoPath = @"C:\Users\Administrator\Desktop\1、练习1-3.avi";
            string videoNewPath = @"C:\Users\Administrator\Desktop\1.avi";
            //创建读取流
            using (FileStream fsread=new FileStream(videoPath,FileMode.Open,FileAccess.Read))
            {
                //创建写入流
                using (FileStream fswrite=new FileStream(videoNewPath,FileMode.OpenOrCreate,FileAccess.Write))
                {
                      while (true)
                  {
                    //返回实际读取到的字节
                    int r = fsread.Read(bytsize, 0, bytsize.Length);
                   //当字节位0的时候 证明已经读取结束
                    if(r==0){
                        break;
                    }
                    fswrite.Write(bytsize,0,r);
                     }
         }
            
        }

FileInfo

提供用于创建、复制、删除、移动和打开文件的属性和实例方法,并且帮助创建 System.IO.FileStream 对象。 此类不能被继承。

      FileInfo myfile = new FileInfo(@"D:\实验路径\新建文本文档.txt");
        myfile.CopyTo(@"D:\实验路径\新建文件夹\新建文本文档2.txt");

DirectoryInfo

公开用于通过目录和子目录进行创建、移动和枚举的实例方法。 此类不能被继承。

DirectoryInfo di = new DirectoryInfo(path);
            DirectoryInfo[] arr = di.GetDirectories();

StreamWriter

 using (StreamWriter sw = new StreamWriter(path))
            {
                sw.Write(textBox2.Text);
            }

StreamReader类

  using (StreamReader sr = new StreamReader(path))
                    {
                        while (!sr.EndOfStream)//获取一个值,该值指示当前的流位置是否在流结尾。
                        {
                            textBox2.AppendText(sr.ReadLine() + "\r\n");
                        }
                    }

Path类

驱动器盘符、目录名、文件名、文件扩展名和分隔符等。

Path类的静态属性和方法,此类操作不影响物理文件。

1、属性

Path类的常用字段成员有PathSeperator(路径分隔符,如”;”)、DirectorySeparatorChar(目录分隔符,如”\”)、VolumeSeparator(卷分隔符,如”:”)、AltDirectorySeparator(替换目录分隔符,如”/”),常用的方法成员有GetDirectoryName(取目录名)、GetFileName(取文件名)

2、方法

GetExtension(取文件扩展名)、GetFullPath(取完整路径)、GetTempPath(取操作系统的临时文件路径)等

例如,以下代码表示提取并显示路径中的目录名和文件名。

string filePath =@”c:\folder\file.txt”;

Path.ChangeExtension(filePath, ".html");// c:\folder\file.html
//合并两个文件路径字符串
Path.Combine("c:\folder", "file.txt");// c:\folder\file.txt
Path.IsPathRooted(filePath);    // true
Path.GetPathRoot(filePath);     // C:\
//得到文件的文件夹路径。
Path.GetDirectoryName(filePath);// c:\folder
//从路径字符串中得到文件名(带扩展名)
Path.GetFileName(filePath);     // file.txt
//从路径字符串中得到文件名(不带扩展名)。
Path.GetFileNameWithoutExtension(filePath);// file
//从文件路径字符串中得到文件的扩展名。
Path.HasExtension(filePath);    // true
Path.GetExtension(filePath);    // .txt
//从文件字符串中得到包括文件名和扩展名的全路径名。
Path.GetFullPath(filePath);     // c:\folder\file.txt

4.XmlDocument创建Xml文档

示例:创建

XmlDocument graxml = new XmlDocument();

                    //1,创建第一个行描述信息, 并且添加到graxml文档中
                    XmlDeclaration ut = graxml.CreateXmlDeclaration("1.0", "utf-8", null);
                    graxml.AppendChild(ut);
                    //2,创建根节点,并将根节点加入到文档中
                    XmlElement gra = graxml.CreateElement("gra");
                    graxml.AppendChild(gra);
                    for (int i = 0; i < data.graAry.Count; i++)
                    {
                        //3,给根节点gra创建子节点, 并将graDetails加入根节点
                        XmlElement graDetails = graxml.CreateElement("graDetails");
                        gra.AppendChild(graDetails);

                        //4, 给graDetails添加子节点
                        XmlElement graId = graxml.CreateElement("graId");
                        graId.InnerText = data.graAry[i].graId;
                        graDetails.AppendChild(graId);

                        XmlElement graDes = graxml.CreateElement("graDes");
                        graDes.InnerText = data.graAry[i].graDes;
                        graDetails.AppendChild(graDes);
                    }
                    graxml.Save("graData.xml");

示例:获取

 string load = "graData.xml";
                try
                {
                    ///年级
                    XmlDocument graxml = new XmlDocument();
                    //graxml.Load("GraData.xml");
                    graxml.Load(load);

                    XmlNode root = graxml.ChildNodes[1];
                    XmlNodeList list = root.ChildNodes;

                    foreach (XmlNode gra in list)
                    {
                        XmlElement id = gra["graId"];
                        XmlElement des = gra["graDes"];
                        ListViewItem fir = listview.Items.Add(id.InnerText);
                        fir.SubItems.Add(des.InnerText);
                        data.graAry.Add(new GraInfo(id.InnerText, des.InnerText));
                    }
                }
                catch (Exception)
                {
                    ShowMsg("获取年级数据失败,搜寻不到数据源!" + load);

                    return;
                }

5.Json文档

JSON是存储和交换文本的语法,类似于XML

但比XML更小,更容易解析,于XML一样是一种数据格式

JSON是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,更容易编写以及解析

例如

[
{"id":2,"name":"星河爆破","number":999},
{"id":3,"name":"九星连珠","number":9},
{"id":4,"name":"一语成谶","number":999}
]

二、JSO语法规则

  • 数据保存在键值对中

  • 数据由逗号分割

  • 花括号保存对象

  • 方括号保存数组

  • 主要是使用的是LitJSON或者Newtonsoft.Json,LitJSON使用NuGet安装,界面如下

示例:创建

 List<GraInfo> list = new List<GraInfo>();
                    //存Json文档
                    for (int i = 0; i < data.graAry.Count; i++)
                    {
                        GraInfo gra = new GraInfo(data.graAry[i].graId, data.graAry[i].graDes);
                        list.Add(gra);
                    }
                    string str = JsonMapper.ToJson(list);
                    using (FileStream fs = new FileStream("graData.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite))
                    {
                        byte[] arr = Encoding.UTF8.GetBytes(str);
                        fs.Write(arr, 0, arr.Length);
                    }

示例:获取

string load = "graData.txt";
                try
                {
                   
                    //年级
                    JsonData gra = JsonMapper.ToObject(File.ReadAllText(load));
                    string str = File.ReadAllText(load);
                    foreach (JsonData item in gra)
                    {
                        //GraInfo info = new GraInfo();
                        string id = (string)item["graId"];
                        string name = (string)item["graDes"];

                        ListViewItem fir = listview.Items.Add(id);
                        fir.SubItems.Add(name);
                        data.graAry.Add(new GraInfo(id, name));

                    }

                }
                catch (Exception)
                {
                    ShowMsg("获取年级数据失败,搜寻不到数据源!" + load);

                    return;
                }

关于内存回收

如果是托管类型且不包含非托管类型成员,你不用管他,. net自有垃圾回收机制

IDisposable.Dispose(): 执行与释放或重置非托管资源关联的应用程序定义的任务。

GC.Collect(): 强制对所有代进行即时垃圾回收。

GC类垃圾回收

界面编程之判断界面是否建立完成

  1. 使用 IsHandleCreated 获取控件或者界面是否建立完成,建立完成后才能使用相关的控件,防止使用invoke 或者beginInvoke 报错;

单例模式

单例就是不能够让用户创建多个对象

单例模式是比较常见的一种设计模式,目的是保证一个类只能有一个实例,而且自行实例化并向整个系统提供这个实例,避免频繁创建对象,节约内存。

比如 Form frm = new Form() ,Form obj1 = new Form() ,这里就创建了两个Form类的对象

常用的单列模式的创建一般都是,私有化构造器,然后在类中提供公共方法来创建对象,实现单列模式。

用winform简单实现更直观

public partial class Form2 : Form { private static Form2 fm2 { set; get; }

    public static  Form2 getSingle() //提供方法创建对象
    {
        return fm2 == null ? fm2 = new Form2() : fm2; //返回类的对象,如果已经new了对象就直接返回,没有在创建
    }
    private Form2() //私有化构造器
    {
        InitializeComponent();
    }
}

Form2,私有化了构造器,提供了一个getSingle()方法来返回对象

public partial class MainForm : Form
{
    public MainForm()  {     InitializeComponent();      }

    private void MainForm_Load(object sender, EventArgs e) {}

    private void button1_Click(object sender, EventArgs e)
    {
        Form2 f2 = Form2.getSingle();/
        f2.Show();
    }
}

Winform 实现控件点击拖拽移动位置

winform开发中有时会遇到需要设计自定义布局的情况,我们可以让用户自定义控件位置,最后保存这些位置作为预览方案。

使用方法

 new ControlMoveResize(button1,this);

辅助类代码如下:

        /// <summary>
        /// 使窗口的中的指定控件支持运行时移动
        /// TODO:运行时缩放
        /// </summary>
        public class ControlMoveResize: Component
        {
            #region 私有成员
            bool isMoving = false;
            Point pCtrlLastCoordinate = new Point(0, 0);
            Point pCursorOffset = new Point(0, 0);
            Point pCursorLastCoordinate = new Point(0, 0);
            private Control ctrl = null;
            private ScrollableControl containe = null;
            #endregion
            #region 私有方法
            /// <summary>
            /// 在鼠标左键按下的状态记录鼠标当前的位置,以及被移动组件的当前位置
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void MouseDown(object sender, MouseEventArgs e)
            {
                if (containe == null)
                {
                    return;
                }
                if (e.Button == MouseButtons.Left)
                {
                    isMoving = true;
                    pCtrlLastCoordinate.X = ctrl.Left;
                    pCtrlLastCoordinate.Y = ctrl.Top;
                    pCursorLastCoordinate.X = Cursor.Position.X;
                    pCursorLastCoordinate.Y = Cursor.Position.Y;


                }
            }
            private void MouseMove(object sender, MouseEventArgs e)
            {
                if (containe == null)
                {
                    return;
                }

                if (e.Button == MouseButtons.Left)
                {
                    if (this.isMoving)
                    {
                        ctrl.BringToFront();
                        Point pCursor = new Point(Cursor.Position.X, Cursor.Position.Y);

                        pCursorOffset.X = pCursor.X - pCursorLastCoordinate.X;

                        pCursorOffset.Y = pCursor.Y - pCursorLastCoordinate.Y;
                        ctrl.Left = pCtrlLastCoordinate.X + pCursorOffset.X;
                        ctrl.Top = pCtrlLastCoordinate.Y + pCursorOffset.Y;
                        ctrl.Parent.Refresh();
                    }

                }
            }

            private void MouseUp(object sender, MouseEventArgs e)
            {
                if (containe == null)
                {
                    return;
                }
                if (this.isMoving)
                {
                    if (pCursorOffset.X == 0 && pCursorOffset.Y == 0)
                    {
                        return;
                    }
                    if ((pCtrlLastCoordinate.X + pCursorOffset.X + ctrl.Width) > 0)
                    {
                        ctrl.Left = pCtrlLastCoordinate.X + pCursorOffset.X;
                    }
                    else
                    {
                        ctrl.Left = 0;
                    }
                    if ((pCtrlLastCoordinate.Y + pCursorOffset.Y + ctrl.Height) > 0)
                    {
                        ctrl.Top = pCtrlLastCoordinate.Y + pCursorOffset.Y;
                    }
                    else
                    {
                        ctrl.Top = 0;
                    }
                    pCursorOffset.X = 0;
                    pCursorOffset.Y = 0;
                }
                moveDownAction?.Invoke();
            }
            #endregion

            public Action moveDownAction;

            #region 构造函数
            /// <summary>
            /// 获取被移动控件对象和容器对象
            /// </summary>
            /// <param name="c">被设置为可运行时移动的控件</param>
            /// <param name="parentContain">可移动控件的容器</param>            
            public ControlMoveResize(Control c, ScrollableControl parentContain)
            {
                ctrl = c;
                this.containe = parentContain;

                ctrl.MouseDown +=new MouseEventHandler(MouseDown);
                ctrl.MouseMove += new MouseEventHandler(MouseMove);
                ctrl.MouseUp += new MouseEventHandler(MouseUp);
            }
            #endregion
            /// <summary>
            /// 取消事件
            /// </summary>
            public void Delete()
            {                
                ctrl.MouseDown -= new MouseEventHandler(MouseDown);
                ctrl.MouseMove -= new MouseEventHandler(MouseMove);
                ctrl.MouseUp -= new MouseEventHandler(MouseUp);
            }                      
        }                            

第二种释放资源

 /// <summary>
        /// 使窗口的中的指定控件支持运行时移动
        /// TODO:运行时缩放
        /// </summary>
        public class ControlMoveResize :IDisposable
        {
            #region 私有成员
            bool isMoving = false;
            Point pCtrlLastCoordinate = new Point(0, 0);
            Point pCursorOffset = new Point(0, 0);
            Point pCursorLastCoordinate = new Point(0, 0);
            private Control ctrl = null;
            private ScrollableControl containe = null;
            #endregion
            #region 私有方法
            /// <summary>
            /// 在鼠标左键按下的状态记录鼠标当前的位置,以及被移动组件的当前位置
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void MouseDown(object sender, MouseEventArgs e)
            {
                if (containe == null)
                {
                    return;
                }
                if (e.Button == MouseButtons.Left)
                {
                    isMoving = true;
                    pCtrlLastCoordinate.X = ctrl.Left;
                    pCtrlLastCoordinate.Y = ctrl.Top;
                    pCursorLastCoordinate.X = Cursor.Position.X;
                    pCursorLastCoordinate.Y = Cursor.Position.Y;


                }
            }
            private void MouseMove(object sender, MouseEventArgs e)
            {
                if (containe == null)
                {
                    return;
                }

                if (e.Button == MouseButtons.Left)
                {
                    if (this.isMoving)
                    {
                        ctrl.BringToFront();
                        Point pCursor = new Point(Cursor.Position.X, Cursor.Position.Y);

                        pCursorOffset.X = pCursor.X - pCursorLastCoordinate.X;

                        pCursorOffset.Y = pCursor.Y - pCursorLastCoordinate.Y;
                        ctrl.Left = pCtrlLastCoordinate.X + pCursorOffset.X;
                        ctrl.Top = pCtrlLastCoordinate.Y + pCursorOffset.Y;
                        ctrl.Parent.Refresh();
                    }

                }
            }

            private void MouseUp(object sender, MouseEventArgs e)
            {
                if (containe == null)
                {
                    return;
                }
                if (this.isMoving)
                {
                    if (pCursorOffset.X == 0 && pCursorOffset.Y == 0)
                    {
                        return;
                    }
                    if ((pCtrlLastCoordinate.X + pCursorOffset.X + ctrl.Width) > 0)
                    {
                        ctrl.Left = pCtrlLastCoordinate.X + pCursorOffset.X;
                    }
                    else
                    {
                        ctrl.Left = 0;
                    }
                    if ((pCtrlLastCoordinate.Y + pCursorOffset.Y + ctrl.Height) > 0)
                    {
                        ctrl.Top = pCtrlLastCoordinate.Y + pCursorOffset.Y;
                    }
                    else
                    {
                        ctrl.Top = 0;
                    }
                    pCursorOffset.X = 0;
                    pCursorOffset.Y = 0;
                }
                moveDownAction?.Invoke();
            }
            #endregion

            public Action moveDownAction;

            #region 构造函数
            /// <summary>
            /// 获取被移动控件对象和容器对象
            /// </summary>
            /// <param name="c">被设置为可运行时移动的控件</param>
            /// <param name="parentContain">可移动控件的容器</param>            
            public ControlMoveResize(Control c, ScrollableControl parentContain)
            {
                ctrl = c;
                this.containe = parentContain;

                ctrl.MouseDown += new MouseEventHandler(MouseDown);
                ctrl.MouseMove += new MouseEventHandler(MouseMove);
                ctrl.MouseUp += new MouseEventHandler(MouseUp);
            }
            #endregion
            /// <summary>
            /// 取消事件
            /// </summary>
            public void Delete()
            {
                ctrl.MouseDown -= new MouseEventHandler(MouseDown);
                ctrl.MouseMove -= new MouseEventHandler(MouseMove);
                ctrl.MouseUp -= new MouseEventHandler(MouseUp);
            }

            #region IDisposable Support
            private bool disposedValue = false; // 要检测冗余调用

            protected virtual void Dispose(bool disposing)
            {
                if (!disposedValue)
                {
                    if (disposing)
                    {
                        // TODO: 释放托管状态(托管对象)。
                    }

                    // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                    // TODO: 将大型字段设置为 null。

                    disposedValue = true;
                }
            }

            // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
            // ~ControlMoveResize()
            // {
            //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            //   Dispose(false);
            // }

            // 添加此代码以正确实现可处置模式。
            public void Dispose()
            {
                // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
                Dispose(true);
                // TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
                // GC.SuppressFinalize(this);
            }
            #endregion
        }

Winform绘制线

 Graphics GPS = this.CreateGraphics();
            Pen MyPen = new Pen(Color.Red, 2f);
            GPS.DrawLine(MyPen, 50,20,300,200);

//创建Graphics对象
            Graphics GPS = this.CreateGraphics();
            //创建黑色pen对象
            Pen MyPen = new Pen(Color.Black, 2f);
            //确定起点和终点
            Point pt1 = new Point(70, 20);
            Point pt2 = new Point(200, 320);
            //使用DrawLine方法绘制直线
            GPS.DrawLine(MyPen, pt1, pt2);

Graphics g=button对象的名字.CreateGraphics();//绑定的控件上
g.DrawLine(new Pen(Color.Red),0,0,10,10);//这行码用于画线,线条起点为按钮左上角。使用控件对象的CreateGraphics方法,创建的Graphics对象可以在控件上画线

线程

在C#中,如果需要进行耗时操作或者并发操作,应该使用后台线程(Background Thread)而不是主线程。主线程是负责处理用户界面和响应用户交互的,如果在主线程上进行耗时或者阻塞操作,会导致程序的卡顿或者无响应。

后台线程可以使用Thread类、Task类等开启,它们会在后台运行,并且不会阻塞主线程的执行。使用后台线程可以避免阻塞主线程,提高程序的响应速度和性能。当后台线程完成任务后,可以将结果通知给主线程,更新UI等操作也可以通过这种方式进行。

需要注意的是,在使用多线程进行并发操作时,需要特别注意线程安全和同步机制,以避免出现数据混乱等问题。常用的同步机制有锁(Lock)、信号量(Semaphore)和互斥量(Mutex)等。

总之,一般来说,我们应该使用后台线程来进行耗时操作和并发操作,避免阻塞主线程。同时,还需要特别关注线程安全和同步机制,以保证程序的正确性和稳定性。

跨线程访问UI控件

1.通过设置窗体属性,取消线程间的安全检查。(最简单,最省事,也是最不负责任的一种)

Control 定义控件的基类,控件是带有可视化表示形式的组件。

属性:

CheckForIllegalCrossThreadCalls 获取或设置一个值,该值指示是否捕获对错误线程的调用,这些调用在调试应用程序时访问控件的 System.Windows.Forms.Control.Handle

 Control.CheckForIllegalCrossThreadCalls = false;

2.通过设置UI控件的Invoke和BeginInvoke方法实现更新。

这个方法是目前跨线程更新UI使用的主流方法,使用控件的Invoke/BegainInvoke方法,将委托转到UI线程上调用,实现线程安全的更新

Invoke: 在拥有此控件的基础窗口句柄的线程上执行指定的委托。

 lb_MainName.Invoke(new Action(() => { lb_MainName.Text = i.ToString(); }));

3.通过UI线程的SynchronizationContext的Post/Send方法更新。

使用线程的同步上下文 SynchronizationContext

原理是,在线程执行过程中,需要更新到UI控件上的数据不再直接更新,而是通过UI线程上下文的Post/Send方法,将数据以异步/同步消息的形式发送到UI线程的消息队列;UI线程收到该消息后,根据消息是异步消息还是同步消息来决定通过异步/同步的方式调用SetTextSafePost方法直接更新自己的控件了。

SynchronizationContext: 提供在各种同步模型中传播同步上下文的基本功能。

Current: 获取当前线程的同步上下文。

Post:在派生类中重写时,将异步消息分派到同步上下文。

 SynchronizationContext SyncContext = null;
       
        public Form()
        {
            InitializeComponent();
            SyncContext = SynchronizationContext.Current;
        }
private void Form_Load(object sender, EventArgs e)
        {           
                SyncContext.Post(p=>
                {
                    lb_MainName.Text = "hello world";
                },null);
		}

Thread线程

睡眠

 Thread.Sleep(200);
  1. ///使用方式
    
    1.
     Thread t10 = new Thread(() => {
                    Thread.Sleep(1000);
                    Console.WriteLine("T10线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
                });
     t10.Start();//启动
    
    2.
     Thread ttt = new Thread(M1);//里面放的是方法  
    ttt.Start();
    
    

线程生命周期

程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。

下面列出了线程生命周期中的各种状态:

未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。 不可运行状态:下面的几种情况下线程是不可运行的: 已经调用 Sleep 方法 已经调用 Wait 方法 通过 I/O 操作阻塞 死亡状态:当线程已完成执行或已中止时的状况

阻塞

  1. 调用Join方法,就可以等待另一个线程结束 可以理解为“等待该线程终止”,也就是在子线程调用了Join()方法后面的代码,只有等到子线程结束了才能执行。

例子

public static void Main()
{
    Thread t=new Thread(Go);
    t.Start();
    t.Join();
    Console.WriteLine("Thread t has ended!");
}

public static void Go()
{
    for(int i=0;i<1000;i++)
    {
        Console.Write("y");
    }
}

可设置超时

 比如:t.Join(500);//阻塞500毫秒

Thread.Sleep()方法会暂停当前的线程,并等一段时间,参数可以时毫秒,也可以是TimeSpan

  1. 如果线程的执行由于某种原因被导致暂定,那么就认定该线程被阻塞了 例如在Sleep()或者通过Join()等待其它线程的结束

  2. 被阻塞的线程会立即将其处理器的时间片生成给其它线程,从此不会再消耗处理器时间,直到满足其阻塞条件为止 可以通过ThreadState这个属性来判断线程是否处于被阻塞的状态 bool blocked=(someThread.ThreadState & ThreadState.WaitSleepJoin)!=0; 必须这样写

ThreadState

  1. ThreadState是一个flags enum,通过按位的形式,可以合并数据的选项

  2. 但是它大部分的枚举值都没什么用,下面的代码将ThreadState剥离为四个最有用的值之一:Unstarted、Running、WaitSleepJoin和Stopped

public static ThreadState SimpleThreadState(ThreadState ts)
{
    return ts&(ThreadState.Unstarted|ThreadState.WaitSleepJoin|ThreadState.Stopped);
}
  1. ThreadState属性可用于诊断的目的,但不适用于同步,因为线程状态可能会在测试ThreadState和对该信息进行操作之间发生变化

当遇到下列四种情况的时候,就会解除阻塞: 阻塞条件被满足 操作超时(如果设置超时的话) 通过Thread。Interrupt()进行打断 通过Thread.Abort()进行中止

ThreadPool线程池

示例放入方法:

ThreadPool.QueueUserWorkItem(new WaitCallback(Receive));

一.设置线程池的最大最先线程数量

 ThreadPool.SetMaxThreads(16,16);// 设置线程池最大线程数量
 ThreadPool.SetMinThreads(8, 8);
 ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);// 获取线程池最大线程数量
 ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);

二.简单使用lambda表达式线程池

 
// 将线程丢进线程池,参数是线程要做的事情            
ThreadPool.QueueUserWorkItem(t =>
{
      DoSomething("threadPool");
});

线程池的优点

1)降低资源消耗,尽最大可能的避免线程的创建和销毁带来的性能开销。

2) 提高响应速度,当任务来时可以直接使用,不用等待线程创建(在线程池的线程未被全部占用的时候,可以直接进行运算)

3)避免大量的线程间因互相抢占系统资源导致的阻塞现象。(线程池会自动管理线程)

4}提高线程的可管理性。(线程池会自动管理线程)

线程的开启会在时间和空间上有不小的开销。所以一般不要随便开,一般不要大于cpu核心数

如果要使用多线程的话,尽量不要自己去开启,使用ThreadPool,它会根据cpu的核心数去开启一个最合适的线程数量

如果你操作的业务,很耗时,就不要使用线程池

Task任务线程

///使用方式

1.
 Task t10 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("T10线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            });
 t10.Start();//启动

2.
 Task ttt = new Task(M1);//里面放的是方法  
ttt.Start();

3. 
     Task.Run(M1);//里面放的是方法  
Wait();//阻塞

阻塞

 Task t10 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("T10线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            });

            Task t11 = new Task(() => {
                Thread.Sleep(2000);
                Console.WriteLine("T11线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            });
            Task t12 = new Task(() => {
                Thread.Sleep(3000);
                Console.WriteLine("T12线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            });
            t10.Start();//启动
            t11.Start();
            t12.Start();
            
            //t11.Wait();//当前任务执行完才执行后续代码
            Task.WaitAll(t10,t11,t12);//里面的全部执行完才执行后续代码

            Task.WaitAny(t12);//里面的任意一个执行完才执行后续代码
            Task t = new Task(() =>
            {
                Console.WriteLine("8888");
            });
         t.Start();
 //方式1 :逐个等待任务完成[不推荐]
    //t10.Wait();
    //t12.Wait();

    //方式2 等待所有任务结束才继续后续代码【推荐】
    //Task.WaitAll(t11, t12);

    //方式3等待任何一个完成即可【推荐】
    //Task.WaitAny(t11,t12);      

线程延续(WhenAll/WhenAny)

WhenAll //创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task 对象都完成时完成。

WhenAny //创建一个任务,该任务将在数组中的任意 一个System.Threading.Tasks.Task 对象完成时完成。

示例:

 Task.WhenAll(t12).ContinueWith((task) =>
            {
                Console.WriteLine("Task:" + task.Id);
            });

长时间任务运行

TaskCreationOptions.LongRunning

Task t1 = new Task(() =>
{
	Thread.Sleep(1000);
	Console.WriteLine("t1线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
},TaskCreationOptions.LongRunning);

TaskCreationOptions.LongRunning:如果你明确知道长时间运行一个业务,就加上这个参数;

也可以使用Thread;

不建议使用ThreadPool;因为pool是不归还线程,系统会强制开启新的线程;一定程度会影响性能;

父子运行

//Task.Factory.StartNew //创建并启动 System.Threading.Tasks.Task。

//TaskCreationOptions.AttachedToParent, //指定将任务附加到任务层次结构中的某个父级

示例:

 Task TT = new Task(() => {
                Task t1 = new Task(() => {
                    Thread.Sleep(1000);
                    Console.WriteLine("T1的ID:" + Thread.CurrentThread.ManagedThreadId);
                }, TaskCreationOptions.AttachedToParent);
                t1.Start();
                Task t3 = Task.Factory.StartNew(() => { //Task.Factory.StartNew 创建并启动 System.Threading.Tasks.Task。
                    Thread.Sleep(2000);
                    Console.WriteLine("T2的ID:" + Thread.CurrentThread.ManagedThreadId);
                }, TaskCreationOptions.AttachedToParent); //TaskCreationOptions.AttachedToParent,指定将任务附加到任务层次结构中的某个父级
            });
            TT.Start();
            TT.Wait();

Task的取消功能

CancellationTokenSource // 向应该被取消的 CancellationToken 发送信号。

属性IsCancellationRequested**获取是否已请求取消此 System.Threading.CancellationTokenSource。

方法 Cancel() //传达取消请求。

示例 开关取消:

CancellationTokenSource cts = new CancellationTokenSource();// 传播有关应取消操作的
            Task t1 = Task.Factory.StartNew(()=> {
                while (!cts.IsCancellationRequested)
                {
                    Thread.Sleep(500);
                    Console.WriteLine("正在执行M4任务中...");
                }
            });

            Thread.Sleep(5000);
            cts.Cancel(); //传达取消请求。
            Console.WriteLine("点击取消按钮,执行以下代码");

示例 自动取消:

CancellationTokenSource cts = new CancellationTokenSource(5000);// 传播有关应取消操作的,通过构造输入毫秒,计时到了后取消
            Task t1 = Task.Factory.StartNew(()=> {
                while (!cts.IsCancellationRequested)//获取是否已请求取消此 System.Threading.CancellationTokenSource。
                {
                    Thread.Sleep(500);
                    Console.WriteLine("正在执行M4任务中...");
                }
            });
                    
            Console.WriteLine("点击取消按钮,执行以下代码");

取消后的操作:**

属性Token 获取 与此CancellationToken有关联的CancellationTokenSource,

方法Register();// 注册一个将在取消此 CancellationToken 时调用的委托。

Token.Register();操作

  cts.Token.Register(()=> {
                Console.WriteLine("取消...后的操作");
            });

Task 暂停,继续,结束

类:

ManualResetEvent,用一个指示是否将初始状态设置为终止的布尔值初始化

方法:

Set(); //启动

Reset();//暂停

类:

CancellationTokenSource 向应该被取消的 CancellationToken 发送信号。

方法:

Cancel(); //结束

属性:

Token //获得与此CancellationToken关联的CancellationTokenSource**

Token** 的属性IsCancellationRequested**获取是否已请求取消此

类:

Control 定义控件的基类,控件是带有可视化表示形式的组件。

属性:

CheckForIllegalCrossThreadCalls 获取或设置一个值,该值指示是否捕获对错误线程的调用,这些调用在调试应用程序时访问控件的 System.Windows.Forms.Control.Handle

CancellationToken 传播有关应取消操作的通知。

  CancellationTokenSource tokenSource = new CancellationTokenSource();
        //信号机制
        ManualResetEvent resetEvent = new ManualResetEvent(true);

 resetEvent.Set(); //启动

 resetEvent.Reset();//暂停

 tokenSource.Cancel(); //结束


Control.CheckForIllegalCrossThreadCalls = false;
            CancellationToken token = tokenSource.Token;
            Task task = new Task(() => {
                int i = 0;
                while (true)
                {
                    if (token.IsCancellationRequested)
                    {
                        return;
                    }
                    resetEvent.WaitOne();
                    //dosoming
                    textBox1.Text = "" + (i++);
                    Task.Delay(100);
                }
            });
            task.Start();

Async 和 Await 的用法详解

异步使用(async和await)

你可以在任何你想用的同步过程中,使用这个关键字。特别是IO操作中

private void button2_Click(object sender, EventArgs e)
{
    //卡界面
    WebClient web = new WebClient();
    string con = web.DownloadString("https://www.znsd.com");
    textBox5.Text = con;
}

对比(上面代码卡死界面;修改为下面的)

async private void button2_Click(object sender, EventArgs e)
{
    //不卡界面
    WebClient web = new WebClient();
    string con = await web.DownloadStringTaskAsync("https://www.znsd.com");
    textBox5.Text = con;
}

编写自己写异步方法

1.无返回值

public async void getData1(){
    
}

2.无返回值,带Task

  • 对于任何异步函数,你可以使用Task替代void作为返回值,让该方法成为更有效的异步(可以进行await )

public async Task getData2() {
    await Task.Delay(10000);
}

当别人调用getData2;如果使用await就要用Task替代void;因为语法要求不可await对应void

那么,问题来了?我们也没有返回Task,怎么就没有语法问题呢,这里是编译器底层帮我们生成一个Task

示例2:

static string GetTxt()
{
    FileStream fs = new FileStream("znsd.txt",FileMode.Open);
    var contents = new byte[fs.Length];

    int c = fs.Read(contents,0,contents.Length);
    Console.WriteLine("个数:"+c);

    return Encoding.UTF8.GetString(contents);
}

改造:我们使用异步去读取

async static Task<string> GetTxt()
{
    FileStream fs = new FileStream("znsd.txt", FileMode.Open);
    var contents = new byte[fs.Length];

    await fs.ReadAsync(contents, 0, contents.Length);
    //接下来可以有多个 await

    return Encoding.UTF8.GetString(contents);
}

async修饰的方法不见得是异步;只是内部可能有异步操作

以上代码,我们在写异步的时候,就是和同步一样的感觉!!!这样我们就可以快速写出异步代码,提升开发效率

小结:到这里Task,async/await的简单使用已经基本结束了。

通过上边的介绍,我们知道async/await是基于Task的,而Task是对ThreadPool的封装改进,主要是为了更有效的控制线程池中的线程(ThreadPool中的线程,我们很难通过代码控制其执行顺序,任务延续和取消等等);ThreadPool基于Thread的,主要目的是减少Thread创建数量和管理Thread的成本。

async/await Task是C#中更先进的,也是微软大力推广的特性,我们在开发中可以尝试使用Task来替代Thread/ThreadPool,处理本地IO和网络IO任务是尽量使用async/await来提高任务执行效率。

async修饰符

async修饰符会让编译器吧await当做关键字而不是标识符(c#5以前可能会使用await作为标识符)

async修饰符只能应用于方法(包括lambda表达式)

该方法可以返回voId,Task,Task<TResult>

async修饰符对方法的前面或public元数据没有影响(和unsafe一样),它只会影响方法内部。

在使用接口内使用async是没有意义的

使用async来重载async的方法却是合法的(只是方法签名一致)

使用了async修饰符的发发就是“异步函数”。

同步锁指的是一种用于保护共享资源的同步机制。当多个线程需要同时访问某些共享资源时,为了避免出现数据竞争和不一致等问题,我们可以使用同步锁来限制只有一个线程可以访问该资源,从而保证资源的正确性和稳定性。

在C#中,常见的同步锁有Mutex、Monitor和Lock三种,它们都能够实现对共享资源的控制和保护,但具体实现方式有所不同:

  1. Mutex:Mutex是一种内核对象,用于保护共享资源的访问。当一个线程尝试获取Mutex并且此时Mutex已经被占用,那么该线程就会进入阻塞状态,直到Mutex被释放。Mutex可以跨进程使用,但由于需要进行系统调用,因此使用起来相对较慢。

  2. Monitor:Monitor是C#中的一种同步锁机制,它使用管程(Monitor)来保护共享资源的访问。使用Monitor机制时,线程可以通过Monitor.Enter()方法来获取锁,并通过Monitor.Exit()方法来释放锁。使用Monitor的好处在于它是CLR提供的一种轻量级同步机制,因此比Mutex要快。

  3. Lock:Lock是C#中的一种语言级别的同步锁机制,它是对Monitor的一种封装,可以通过lock关键字来快速获得和释放锁。Lock机制是C#开发中使用广泛的一种同步机制,它比Monitor更简单易用,并且在性能上也相对较好。

需要注意的是,在使用同步锁时,需要考虑线程安全和死锁等问题,并合理地使用各种机制来保证程序的正确性和可靠性。


Monitor锁

Monitor是C#中一种同步机制,用于保护共享资源的访问。当多个线程需要同时访问某些共享资源时,可以使用Monitor来控制只有一个线程可以访问该资源,从而保证程序的正确性和稳定性。

下面是Monitor的详细讲解:

  1. Enter/Exit:Enter方法用于获取锁,进入临界区;Exit方法用于释放锁,退出临界区。在获取锁之前,如果已经有其他线程占用了锁,则当前线程会被阻塞,直到获取到锁。在释放锁之后,其他线程才能够获得锁进行操作。

示例代码如下:

复制代码Monitor.Enter(lockObject); // 获取锁
try
{
    // 进入临界区,访问共享资源
}
finally
{
    Monitor.Exit(lockObject); // 释放锁
}
  1. TryEnter/Exit:TryEnter方法可以尝试获取锁,如果锁已经被占用,则该方法会立即返回false,否则它会获取锁并返回true。这个方法非常有用,因为它避免了线程阻塞。TryEnter方法还可以设置超时时间,以避免线程长时间等待锁。

示例代码如下:

复制代码if (Monitor.TryEnter(lockObject))
{
    try
    {
        // 进入临界区,访问共享资源
    }
    finally
    {
        Monitor.Exit(lockObject); // 释放锁
    }
}
else
{
    // 锁已被占用,可以进行其他操作或等待一段时间后再尝试获取锁
}
  1. Wait:Wait方法可以使线程进入等待状态,并释放锁。当其他线程调用Pulse/PulseAll方法时,该线程会被唤醒并重新获取锁。使用Wait和Pulse方法可以实现线程间通信,以便更好地控制流程。

示例代码如下:

复制代码Monitor.Enter(lockObject);
try
{
    while (condition)
    {
        Monitor.Wait(lockObject); // 进入等待状态并释放锁
    }
    // 满足条件后访问共享资源
}
finally
{
    Monitor.Exit(lockObject); // 释放锁
}
  1. Pulse/PulseAll:Pulse方法可以唤醒一个处于等待状态的线程,并重新获取锁。PulseAll方法则可以唤醒所有处于等待状态的线程。

示例代码如下:

复制代码Monitor.Enter(lockObject);
try
{
    condition = true;
    Monitor.PulseAll(lockObject); // 唤醒所有等待锁的线程
}
finally
{
    Monitor.Exit(lockObject);
}

需要注意的是,Monitor是基于管程(Monitor)概念来实现的,因此它只能用于同一个进程内部的线程之间的同步操作。在使用Monitor时,需要特别关注线程安全和死锁等问题,并在程序设计时尽可能地避免共享资源。


Mutex锁

Mutex是C#中一种同步机制,用于保护共享资源的访问。当多个线程需要同时访问某些共享资源时,可以使用Mutex来控制只有一个线程可以访问该资源,从而保证程序的正确性和稳定性。

下面是Mutex的详细讲解:

  1. WaitOne/ReleaseMutex:WaitOne方法用于获取Mutex,即进入临界区;ReleaseMutex方法用于释放Mutex,即退出临界区。在获取Mutex之前,如果已经有其他线程占用了Mutex,则当前线程会被阻塞,直到获取到Mutex。在释放Mutex之后,其他线程才能够获得Mutex进行操作。

示例代码如下:

复制代码Mutex mutex = new Mutex();
mutex.WaitOne(); // 获取Mutex
try
{
    // 进入临界区,访问共享资源
}
finally
{
    mutex.ReleaseMutex(); // 释放Mutex
}
  1. TryWaitOne/ReleaseMutex:TryWaitOne方法可以尝试获取Mutex,如果Mutex已经被占用,则该方法会立即返回false,否则它会获取Mutex并返回true。这个方法非常有用,因为它避免了线程阻塞。TryWaitOne方法还可以设置超时时间,以避免线程长时间等待Mutex。

示例代码如下:

复制代码Mutex mutex = new Mutex();
if (mutex.TryWaitOne())
{
    try
    {
        // 进入临界区,访问共享资源
    }
    finally
    {
        mutex.ReleaseMutex(); // 释放Mutex
    }
}
else
{
    // Mutex已被占用,可以进行其他操作或等待一段时间后再尝试获取Mutex
}

需要注意的是,Mutex是一种内核对象,它可以跨进程使用。因此,在使用Mutex时,要特别关注线程安全和死锁等问题,并确保程序的正确性和稳定性。如果Mutex没有正确地释放,可能会导致系统崩溃或者其他进程无法获得Mutex而出现异常。

另外,Mutex还具有一些高级特性,如互斥级别(MutexLevel)、同步事件(WaitHandle)和命名Mutex等。通过这些特性,我们可以更好地掌控Mutex的使用和管理。


Lock锁

Lock是C#中一种语言级别的同步机制,用于保护共享资源的访问。与Mutex和Monitor不同,Lock是通过CLR提供的内置类型来实现的,因此它更加简单易用,在性能上也相对较好。

下面是Lock的详细讲解:

  1. Lock/Unlock:Lock方法用于获取锁,即进入临界区;Unlock方法用于释放锁,即退出临界区。在获取锁之前,如果已经有其他线程占用了锁,则当前线程会被阻塞,直到获取到锁。在释放锁之后,其他线程才能够获得锁进行操作。

示例代码如下:

复制代码object lockObject = new object();
lock (lockObject) // 获取锁
{
    // 进入临界区,访问共享资源
} // 释放锁

需要注意的是,在使用Lock时,需要特别关注线程安全和死锁等问题,并且应该避免在锁内部进行阻塞或者耗时操作,以及避免在不同的代码块中使用不同的锁,否则可能会导致线程间互相影响而出现数据不一致的问题。

另外,Lock机制也具有一些高级特性,如可重入锁(Reentrant Lock)和ReaderWriterLock等。通过这些特性,我们可以进一步掌控锁的使用和管理,以更好地保证程序的正确性和稳定性。

在C#中,常用的锁包括以下几种:

  1. Monitor Lock:Monitor是一个类,可以使用它的静态方法来获得锁。它使用内部线程同步机制实现,可以避免死锁,因此在C#中最常用的锁就是Monitor Lock。

示例代码如下:

复制代码object lockObject = new object();
lock (lockObject) // 获取锁
{
    // 进入临界区,访问共享资源
} // 释放锁
  1. ReaderWriterLockSlim锁:当需要读取共享资源时,可以使用ReaderWriterLockSlim锁来提高性能。这个锁可以允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。因此,它比简单的Monitor Lock更加灵活和高效。

示例代码如下:

复制代码ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
rwLock.EnterReadLock(); // 获取读锁
try
{
    // 访问共享资源,允许多个线程同时读取
}
finally
{
    rwLock.ExitReadLock(); // 释放读锁
}

rwLock.EnterWriteLock(); // 获取写锁
try
{
    // 访问共享资源,只允许一个线程写入
}
finally
{
    rwLock.ExitWriteLock(); // 释放写锁
}
  1. SemaphoreSlim锁:当系统资源有限时,可以使用SemaphoreSlim锁来控制线程的访问。这个锁允许多个线程同时访问共享资源,但限制了并发的数量。

示例代码如下:

复制代码SemaphoreSlim semaphore = new SemaphoreSlim(initialCount: 3, maxCount: 3); // 最多允许3个线程同时访问
semaphore.Wait(); // 获取信号量
try
{
    // 访问共享资源
}
finally
{
    semaphore.Release(); // 释放信号量
}

需要根据具体的应用场景来选择合适的锁机制,以确保程序的正确性和高效性。

Socket通信

Socket通讯基本流程

需要完成以下几个步骤: 服务器端: 第一步:建立一个用于通信的Socket对象 第二步:使用bind绑定IP地址和端口号 第三步:使用listen监听客户端 第四步:使用accept中断程序直到连接上客户端 第五步:接收来自客户端的请求 第六步:返回客户端需要的数据 第七步:如果接收到客户端已关闭连接信息就关闭服务器端

客户端: 第一步:建立一个用于通信的Socket对象 第二步:根据指定的IP和端口connet服务器 第三步:连接成功后向服务器端发送数据请求 第四步:接收服务器返回的请求数据 第五步:如果还需要请求数据继续发送请求 第六步:如果不需要请求数据就关闭客户端并给服务器发送关闭连接信息

Socket编程函数【作用+示例】:

(1)Socket()

  • 创建Socket对象,构造函数需要输入三个参数,创建客户端和服务器端Socket对象示例如下

Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket构造函数参数
	AddressFamily指定Socket用来解析地址的寻址方案
	SocketType定义要打开的Socket的类型
	ProtocolType:向Windows Sockets API通知所请求的协议

(2)Bind()

  • 绑定一个本地的IP和端口号,参数是一个绑定了IP和端口号的IPEndPoint对象

ServerSocket.Bind(new IPEndPoint(ip,port));
或者
IPEndPoint ipEndPoint = new IPEndPoint(ip,port)
ServerSocket.Bind(ipEndPoint);

(3)Listen()

  • 让Socket侦听传入的连接,参数为指定侦听队列的容量

ServerSocket.Listen(10);

(4)Connect()

  • 建立与远程主机的连接

ClientSocket.Connect(ipEndPoint);

(5)Accept()

  • 接收连接并返回一个新的Socket,Accept会中断程序,直到有客户端连接

Socket socket = ServerSocket.Accept();

(6)Send()

  • 输出数据到Socket

//Encoding.ASCII.GetBytes()将字符串转成字节
byte[] message = Encoding.ASCII.GetBytes("Connect the Server"); //通信时实际发送的是字节数组,所以要将发送消息转换字节
ClientSocket.Send(message);
socket.Send(message);

(7)Receive()

  • 从Socket中读取数据

byte[] receive = new byte[1024];
int length = ClientSocket.Receive(receive); // length 接收字节数组长度
int length = socket.Receive(receive);

(8)Close()

  • 关闭Socket,销毁连接

socket.Close()
ClientSocket.Close()

Modbus通讯

存储区:

输出线圈、输入线圈、输入寄存器、保持寄存器

1bit 0/1 word 2byte

存储区 范围:

格式 Y xxxx


名称对象类型存储区代号5位标准地址6位扩展地址读/写作用
输出线圈单个位0区00001-09999000001-065535读写通过应用系统可改变这种类型数据
输入线圈(离散量输入)单个位1区10001-19999100001-165535只读I/O系统可提供这种类型数据
输入(读)寄存器16位字3区30001-39999300001-365535只读I/O系统可提供这种类型数据
保持(写)寄存器16位字4区40001-49999400001-465535读写通过应用系统可改变这种类型数据

通讯:数据读取/写入

读/写 是一种功能;也就有自己的功能码

  • 读取: 功能码

    读取输出线圈 01H(01)

    读取输入线圈 02H(02)

    读取保持寄存器 03H(03)

    读取输入寄存器 04H(04)

  • 写入:

    预置单线圈 05H(05)

    预置单寄存器 06H(06)

    预置多线圈 0FH(15)

    预置多寄存器 10H(16)

对于读取/写入来说:

从站地址(1byte) :找谁?

功能码(1byte):要干什么?

数据(n*byte) :具体要求怎么做?

校验(2byte):验证所发出报文的正确性

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值