本文是对操作物理机的 WPF 项目的代码学习笔记,涉及的内容包括 C# PLC 通讯等内容。
1. SystemRunningInfo 代码学习
1. 内存和 CPU 使用率
Task.Run(async () =>
{
PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
PerformanceCounter ramCounter = new PerformanceCounter("Memory", "Available MBytes");
await Task.Delay(3000);
while (true)
{
cpuCounter.NextValue();
await Task.Delay(200);
var cpuUsage = cpuCounter.NextValue();
var ramUsage = ramCounter.NextValue();
}
});
2. 字符串和日期的格式化输出
mainWindowViewModel.CpuInfo = $"{cpuUsage:f2}%";
mainWindowViewModel.SystemTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
3. timeSpan 的使用
var timeSpan = (DateTime.Now - MainWindowViewModel.startTime);
// timeSpan.Days timeSpan.Hours timeSpan.Minutes timeSpan.Seconds
4. DNS 上方法
Dns.GetHostName();
Dns.GetHostAddresses(Dns.GetHostName());
5. C# 中的 map 方法
instanceOfList.Select(m => m.ToString()).ToList();
6. Join 方法
string.Join(" ",address); // address as List<string>
7. 继承 AutoRun 的接口会自动被运行
将需要执行的 Task 写在子类的构造函数中,然后,只要实例化就能够保证 Task 被执行。
using System;
using System.Threading.Tasks;
namespace Auto.Demo
{
/// <summary>
/// 继承 IAutoRun 的接口会被自动运行
/// </summary>
public class AutoRunDemo : IAutoRun
{
public AutoRunDemo()
{
Task.Run(async () =>
{
while (true)
{
Console.WriteLine(DateTime.Now);
await Task.Delay(200);
}
});
}
}
}
通过反射可以找到所有实现了 IAutoRun 接口的类,通过一个遍历,逐个实例化它们即可。
using System;
using System.Linq;
using System.Reflection;
namespace Auto.Demo
{
public static class AutoRunner
{
public static void Start()
{
var types = Assembly.GetExecutingAssembly().GetTypes();
var autoRunTypes = types
.Where(t => t.IsClass && !t.IsAbstract && typeof(IAutoRun).IsAssignableFrom(t))
.ToList();
foreach (var type in autoRunTypes)
{
Activator.CreateInstance(type);
}
}
}
}
8. 使用定时器统计计算机资源情况并自动在后台执行
整体代码如下所示:
using Application.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Application.Businesses
{
/// <summary>
/// 应用运行时系统相关信息
/// </summary>
public class SystemRunningInfo : IBase, IAutoRun
{
private IServiceProvider mServiceProvider;
private MainWindowViewModel mainWindowViewModel;
public SystemRunningInfo(IServiceProvider mServiceProvider)
{
Task.Run(async () =>
{
this.mServiceProvider = mServiceProvider;
PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
PerformanceCounter ramCounter = new PerformanceCounter("Memory", "Available MBytes");
await Task.Delay(3000);
mainWindowViewModel = mServiceProvider.GetViewModel<MainWindowViewModel>();
mainWindowViewModel.SystemIPAddrss= GetAllIPAddress();
while (true)
{
cpuCounter.NextValue();
await Task.Delay(200);
var cpuUsage = cpuCounter.NextValue();
var ramUsage = ramCounter.NextValue();
mainWindowViewModel.CpuInfo = $"{cpuUsage:f2}%";
mainWindowViewModel.SystemMemory = $"{ramUsage}MB";
mainWindowViewModel.SystemTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var timeSpan = (DateTime.Now - MainWindowViewModel.startTime);
mainWindowViewModel.RunningTime = $"{timeSpan.Days}天{timeSpan.Hours}小时{timeSpan.Minutes}分{timeSpan.Seconds}秒";
}
});
}
public string GetAllIPAddress()
{
var address = Dns.GetHostAddresses(Dns.GetHostName()).Where(m => m.IsIPv6LinkLocal==false).Select(m => m.ToString()).ToList();
address.Add(Dns.GetHostName());
return string.Join(" ",address);
}
}
}
2. PrintConfig 代码学习
1. Tcp 连接和写值
// 1. 构建 TcpClient 和 NetworkStream
/// <summary>
/// 网络数据流
/// </summary>
NetworkStream NetworkStream;
private TcpClient tcpClient = new TcpClient();
// 2. 连接到服务器(对 TcpClient 和 NetworkStream 赋值然后再连接)
public Result Connect(string hostName = "127.0.0.1", int port = 9998)
{
try
{
Close();
tcpClient = new TcpClient();
tcpClient.Connect(hostName, port);
NetworkStream = tcpClient.GetStream();
return Result.Accept("连接成功");
}
catch (Exception err)
{
return Result.Reject($"连接失败,{err.Message}");
}
}
// 3. 关闭现有连接
public void Close()
{
if (tcpClient != null)
{
tcpClient.Close();
tcpClient.Dispose();
}
}
// 4. 通过网络写值
private void Write<T>(T data)
{
this.Connect();
var str = JsonConvert.SerializeObject(data, Formatting.Indented);
var content = Encoding.UTF8.GetBytes(str);
NetworkStream.WriteByte(0x02);
NetworkStream.Write(content, 0, content.Length);
NetworkStream.WriteByte(0x03);
NetworkStream.Flush();
}
// 5. 读取网络值
private T Read<T>()
{
try
{
byte[] buffer = new byte[4096 * 400];
var length = NetworkStream.Read(buffer, 0, buffer.Length);
if (length >= 0)
{
var array = buffer.Take(length).ToArray();
if (array.FirstOrDefault() == 0x02)
{
array = array.Skip(1).ToArray();
if (array.LastOrDefault() == 0x03)
{
array = array.Take(array.Length - 1).ToArray();
var content = Encoding.UTF8.GetString(array);
var str = JsonConvert.DeserializeObject<T>(content);
return str;
}
}
return default;
}
else
{
this.Connect();
return default;
}
}
catch (Exception)
{
return default;
}
}
上述代码中的细节:
- 数据流封装
NetworkStream.WriteByte(0x02); // 写入起始标志
NetworkStream.Write(content, 0, content.Length); // 写入数据
NetworkStream.WriteByte(0x03); // 写入结束标志
NetworkStream.Flush(); // 刷新流,确保数据发送
- 数据流识别
if (array.FirstOrDefault() == 0x02) // 检查起始标志
{
array = array.Skip(1).ToArray(); // 去掉起始标志
if (array.LastOrDefault() == 0x03) // 检查结束标志
{
array = array.Take(array.Length - 1).ToArray(); // 去掉结束标志
var content = Encoding.UTF8.GetString(array); // 将字节数组转换为字符串
var str = JsonConvert.DeserializeObject<T>(content); // 反序列化为指定类型
return str;
}
}
- 将字节数组转换为字符串
var content = Encoding.UTF8.GetString(array); // 将字节数组转换为字符串
- 从流中读取一定长度的数据
先声明 buffer 然后向 buffer 中读取指定长度。
byte[] buffer = new byte[4096 * 400];
var length = NetworkStream.Read(buffer, 0, buffer.Length);
if (length >= 0)
{
var array = buffer.Take(length).ToArray();
}
- TcpClient 和 NetworkStream 的创建和关闭
tcpClient = new TcpClient();
tcpClient.Connect(hostName, port);
NetworkStream = tcpClient.GetStream();
2. 通过 switch 获取 url
public override string GetUri()
{
string serverPath = null;
switch (ServerPath)
{
case ServerPoint.HangZhou:
serverPath = "http://mes-expose.hikvision.com:12304/ws/pkgPrintConfig";
break;
case ServerPoint.ChongQing:
serverPath = "http://mescq-expose.hikvision.com:12304/ws/pkgPrintConfig";
break;
case ServerPoint.WuHan:
serverPath = "http://himeswh-expose.hikvision.com.cn:12304/ws/pkgPrintConfig";
break;
case ServerPoint.Others:
throw new NotSupportedException("不支持改服务器类型");
default:
break;
}
return serverPath;
}
3. 反序列化获得附加方法
如果 T 是一个类类型,并且 T 上定义了方法 A,那么通过 JsonConvert.DeserializeObject<T>(rawData) 反序列化后得到的对象 res 也会有方法 A。
4. 封装一个 Result 类
看看一个简单的结果,会被封装成多么复杂:
1. ResultCore<TStatus> 类
public abstract class ResultCore<TStatus>
{
// 状态
public TStatus Status { get; set; }
// 构造函数
protected ResultCore(TStatus status)
{
Status = status;
}
// 验证是否成功
protected abstract bool ValidateSuccess(TStatus status);
}
- 说明:
ResultCore<TStatus>是一个泛型抽象类,用于表示一个操作的结果核心。Status属性表示操作的状态,类型为泛型TStatus。- 构造函数
ResultCore(TStatus status)用于初始化Status属性。 ValidateSuccess(TStatus status)是一个抽象方法,用于验证操作是否成功,具体的实现由子类提供。
2. ResultCore 类
public abstract class ResultCore : ResultCore<bool>
{
// 是否出错
public bool IsError { get; set; }
// 是否成功
public bool IsSuccess => !IsError;
// 错误内容
public virtual string Message { get; set; }
// 构造函数
public ResultCore(bool isError, string message = null)
: base(isError)
{
IsError = isError;
Message = message;
}
// 验证是否成功
protected override bool ValidateSuccess(bool status)
{
return !status;
}
}
- 说明:
ResultCore是ResultCore<bool>的具体实现,用于表示一个布尔状态的结果核心。IsError属性表示操作是否出错。IsSuccess属性表示操作是否成功,它是IsError的反值。Message属性用于存储错误信息或成功信息。- 构造函数
ResultCore(bool isError, string message = null)用于初始化IsError和Message属性。 ValidateSuccess(bool status)方法重写了父类的抽象方法,用于验证操作是否成功。在这里,status为false表示成功。
3. Result 类
public class Result : ResultCore
{
// 构造函数
public Result(bool isError, string message)
: base(isError, message)
{
}
// 结果必须大于0
public static Result MustGreaterZero(int result, string failedMessage)
{
if (result > 0)
{
return Accept();
}
return Reject(failedMessage);
}
// 成功结果
public static Result Accept(string message = null)
{
return new Result(isError: false, message ?? "OK");
}
// 失败结果
public static Result Reject(string message)
{
return new Result(isError: true, message);
}
}
- 说明:
Result类是ResultCore的具体实现,用于表示一个操作的结果。- 构造函数
Result(bool isError, string message)用于初始化IsError和Message属性。 MustGreaterZero(int result, string failedMessage)是一个静态方法,用于检查result是否大于 0。如果大于 0,返回成功结果;否则返回失败结果,并附带失败信息。Accept(string message = null)是一个静态方法,用于返回一个成功的结果对象,默认消息为 "OK"。Reject(string message)是一个静态方法,用于返回一个失败的结果对象,并附带失败信息。
总结
ResultCore<TStatus>是一个泛型抽象类,用于定义操作结果的核心结构。ResultCore是ResultCore<bool>的具体实现,用于表示布尔状态的结果。Result类是ResultCore的具体实现,提供了更多的实用方法,如MustGreaterZero、Accept和Reject,用于生成成功或失败的结果对象。
这些类可以用于在应用程序中统一处理操作结果,特别是在需要返回成功或失败状态时,提供了一种结构化的方式来处理和传递结果信息。
注意:
ResultCore : ResultCore<bool>和public ResultCore(bool isError, string message = null) : base(isError)中的: base(isError).: base(isError)是构造函数初始化列表的一部分,用于调用基类(父类)的构造函数。具体来说,它表示当前类的构造函数在执行之前,会先调用基类的构造函数,并将 isError 作为参数传递给基类的构造函数。
3. OPBase 代码学习
1. 字符串比较
"NoRead".Equals(code, StringComparison.OrdinalIgnoreCase)
2. 轮询方式等候资源
var result = new DeviceResult();
if (reader == null)
{
result.IsError = true;
result.Message = "设备连接失败";
}
else
{
var codeRes = await reader.RequestAsync("start");
int count = 0;
while (codeRes.Content.IsError && count++ < repeatTime)
{
await Task.Delay(80);
codeRes = await reader.RequestAsync("start");
}
}
3. 两种构造数据的方式
1. 数据构造方式
第一种方式(GetMaterialMoveOutRequest)
var request = new MesRequestBase<MaterialMoveOutInfo>
{
Id = Guid.NewGuid().ToString(),
MachineId = configuration[$"{opName}:MachineId"],
WorkStation = configuration[$"{opName}:WorkStation"],
Data = new MaterialMoveOutInfo(barcode, workNumber: workNumber)
};
request.Data.CraftData = null;
- 特点:
- 使用对象初始化器:通过对象初始化器语法直接设置
MesRequestBase<MaterialMoveOutInfo>的属性。 - 分步设置属性:在创建对象后,进一步设置
Data.CraftData属性。 - 灵活性高:可以在创建对象后动态调整属性值。
- 使用对象初始化器:通过对象初始化器语法直接设置
第二种方式(GetMaterialMoveInRequest)
var MachineId = configuration[$"{opName}:MachineId"];
var WorkStation = configuration[$"{opName}:WorkStation"];
var request = new RequestWithWknum(WorkStation, MachineId, barcode, workNumber);
- 特点:
- 通过构造函数初始化:直接通过构造函数传递参数,一次性完成对象的初始化。
- 简洁性高:代码更简洁,直接通过构造函数完成所有必要的设置。
4. 生成唯一 id 值
Id = Guid.NewGuid().ToString();
5. C# 版本的 Promise.all
await Task.WhenAll(task);
await Task.WhenAll(task1, task2);
6. Task 上的常用 API 及启动和停止
Task 是 C# 中用于表示异步操作的核心类,属于 System.Threading.Tasks 命名空间。它提供了一种简洁的方式来处理异步编程,支持并发任务的启动、等待和取消。以下是 Task 的常见使用方式和一些重要的 API。
1. 启动任务
1.1 使用 Task.Run 启动异步任务
Task.Run 是启动异步任务的最常用方法,它会将工作项排队到线程池中执行。
Task task = Task.Run(() =>
{
// 执行异步操作
Thread.Sleep(1000); // 模拟耗时操作
Console.WriteLine("Task completed!");
});
1.2 使用 Task.Start 启动任务
Task.Start 是另一种启动任务的方式,但它需要先创建一个 Task 对象,然后手动调用 Start 方法。
Task task = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task completed!");
});
task.Start();
2. 等待任务完成
2.1 使用 await 等待单个任务
await 关键字用于等待任务完成,同时不会阻塞主线程。
await task;
Console.WriteLine("Task has finished.");
2.2 使用 Task.Wait 等待单个任务
Task.Wait 是同步方法,会阻塞当前线程,直到任务完成。
task.Wait();
Console.WriteLine("Task has finished.");
2.3 使用 Task.WhenAll 等待多个任务
Task.WhenAll 用于等待多个任务完成。
Task task1 = Task.Run(() => { /* Task 1 */ });
Task task2 = Task.Run(() => { /* Task 2 */ });
await Task.WhenAll(task1, task2);
Console.WriteLine("All tasks have finished.");
3. 获取任务结果
3.1 使用 Task.Result 获取结果
Task.Result 是同步属性,会阻塞当前线程,直到任务完成,并返回任务的结果。
Task<int> task = Task.Run(() => 42);
int result = task.Result; // 阻塞直到任务完成
Console.WriteLine(result); // 输出:42
3.2 使用 await 获取结果
await 关键字可以用于等待任务完成并获取结果,而不会阻塞主线程。
Task<int> task = Task.Run(() => 42);
int result = await task;
Console.WriteLine(result); // 输出:42
4. 异步方法
4.1 定义异步方法
异步方法使用 async 关键字修饰,并返回 Task 或 Task<T>。
public async Task<int> GetNumberAsync()
{
await Task.Delay(1000); // 模拟异步操作
return 42;
}
4.2 调用异步方法
使用 await 调用异步方法。
int number = await GetNumberAsync();
Console.WriteLine(number); // 输出:42
5. 取消任务
5.1 使用 CancellationToken
CancellationToken 用于取消任务的执行。
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Thread.Sleep(100);
Console.WriteLine("Task is running...");
}
}, token);
// 取消任务
cts.Cancel();
6. 异常处理
6.1 捕获任务中的异常
任务中的异常可以通过 try-catch 块捕获。
try
{
await Task.Run(() =>
{
throw new Exception("Something went wrong!");
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
7. 常见的 Task API
Task.Run:启动一个异步任务。Task.WhenAll:等待多个任务完成。Task.WhenAny:等待多个任务中的任意一个完成。Task.Delay:创建一个延迟任务。Task.FromCanceled:创建一个已取消的任务。Task.FromException:创建一个抛出异常的任务。Task.FromResult:创建一个已完成的任务,返回指定结果。
总结
Task 是 C# 中处理异步编程的核心工具。通过 Task.Run 启动任务,使用 await 等待任务完成,并通过 Task.WhenAll 等方法处理多个任务。Task 提供了丰富的 API,支持异步操作、结果获取、异常处理和任务取消。掌握这些基本用法,可以帮助你更高效地编写异步代码。
4. 创建文件夹保存照片
1. 创建文件夹的代码
public static string CreateFolder(string folderName)
{
if(!Directory.Exists(folderName))
{
Directory.CreateDirectory(folderName);
}
return folderName;
}
2. 通过图片 uri 保存图片至指定位置
public void SaveBitmapImageDirectly(ImageSource imageSource, string filePath, string name)
{
if(imageSource == null) return;
BitmapSource bitmapSource = imageSource as BitmapSource;
name += ".jpg";
string outputFileName = filePath + name;
CreateFolder(filePath);
if(File.Exists(filePath)) File.Delete(filePath);
using(FileStream fileStream = File.Create(filePath))
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(BitmapFrame.Create(bitmapSource)));
encoder.Save(fileStream);
}
}
3. 扫码枪原理
可以简单的将其理解成一种外设,就好像键盘一样是一种文字的输入设备。