杂项
新对象初始化器(C# 9.0+)
{ get; set; } = new() { ... }
在 C# 9.0 及更高版本中,C# 引入了一种新的语法,允许在初始化时使用简化的方式来实例化对象。这种简化的方式省略了类型名(推断类型):
使用 [] 语法初始化
List<ISeries<DateTimePoint>> re =
[
new ColumnSeries<DateTimePoint>() {Name="合格数量",Values=new List<DateTimePoint>()},
new LineSeries<DateTimePoint>() {Name="总数量",Fill=null,Values=new List<DateTimePoint>(),GeometrySize=10},
];实际上在 C# 中是可以被编译器识别并转换为 List 类型的初始化方式的,编译器会根据上下文自动推断它是一个 List,并进行正确的初始化。
可读性差 一致性差
Npcap安装失败,vmware的原因
Npcap Bug Report - error 8007007e · Issue #135 · nmap/npcap · GitHub
类中含有data,以下是data中的数据

配置文件保存
//Environment.SpecialFolder.ApplicationData:
//表示当前用户的应用程序数据文件夹路径(通常简称 AppData
//C:\Users\<用户名>\AppData\Roaming\Lmes
public static string 配置文件路径
{
get
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Lmes");
}
}CancellationToken
Task 支持取消操作,可以通过 CancellationToken 来取消正在进行的异步操作。例如,我们可以在异步方法中检查 CancellationToken 的状态,并在需要时取消操作:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task.Run(async () =>
{
try
{
int i = 0;
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000);
Console.WriteLine($"第{i}次等待");
i += 1;
}
cancellationToken.ThrowIfCancellationRequested();//可弹出OperationCanceledException类型的错误
}
catch (OperationCanceledException)
{
Console.WriteLine($"任务被取消");
}
}, cancellationToken);
Thread.Sleep(5000);
cancellationTokenSource.Cancel();//取消任务
Console.ReadKey();_ = Task.Run(() =>
{
工站任务 = 工站.运行(工站取消令牌.Token);
});
private async void 菜单_断开_Click(object sender, RoutedEventArgs e)
{
日志写入.写入("已经断开", Brushes.Red);
工站取消令牌?.Cancel();
//if (工站任务 != null) await 工站任务;
var notificationContent = new NotificationContent
{
Title = "断开连接",
Message = "断开连接成功.",
Type = NotificationType.Success
};
连接状态 = "PLC连接状态:未连接";
_ = notificationManager.ShowAsync(notificationContent, areaName: nameof(WindowArea));
//await this.ShowMessageAsync("停止成功", "停止成功");
}关键问题与解决方案
实时性
PLC 和 MES 系统的实时性要求高,数据传输可能有延迟:
使用
PeriodicTimer设置定时采集周期(如 300ms)。工控机缓存数据,确保网络故障时不会丢失信号数据。
可靠性
如果通信中断(如工控机与PLC、工控机与MES的连接断开),需要有重连机制:
对 PLC 和 MES 系统分别设计异常重连逻辑。
数据一致性
在多设备、多站点的情况下,数据的时间戳和唯一性要保证:
给每个信号记录时间戳。
所有信号按站点、批次分类。
安全性
工控机和MES系统的接口需要加密,防止数据泄露:
使用 HTTPS 协议。
数据签名机制(如 JWT)。
logger.LogInformation("产能统计服务启动!");
多个工站处理产品
"snNumber" "virtualSN" "stationCode" "scheduleCode" "operationCode" "isbad" "SN000" "SN001" "GZ001" "PC2024052300075" "GX001" true "SN001" " " "0" "PC2024052300075" "GX001" false "SN002" "0" "PC2024052300075" "GX001" false
产线1 工站1 工站2 工站...
当工站1处理完产品1,由工站2处理产品1,工站1继续处理产品2,以此类推
将对象序列化为JSON字符串并存储为文件
public static JsonSerializerOptions 整理格式 { get; set; } = new JsonSerializerOptions()
{
//字符编码
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
//是否缩进
WriteIndented = true,
};File.WriteAllText(Path.Combine(系统参数.配置文件路径, $"{下拉框_工位编号.SelectedItem}.信号表"), JsonSerializer.Serialize(系统参数.测试, 系统参数.整理格式));Uri.EscapeDataString
对 URL 中的参数进行编码,以确保它们在传输过程中不会被误解。如各种的符号
AutoMapper 配置和映射器
//创建 AutoMapper 配置和映射器
var config1 = new MapperConfiguration(cfg =>
{
cfg.CreateMap<生产参数信息请求体Data, ProductParameters>();
});
var mapper1 = config1.CreateMapper();
//映射数据并生成新的 GUID
ProductParameters entity1 = mapper1.Map<ProductParameters>(data.data);
var dataId = Guid.NewGuid();
entity1.Id = Guid.NewGuid();
entity1.dataId = dataId;
//发送 HTTP POST 请求
var re1 = await httpClient.PostAsJsonAsync($"api/生产参数信息/新增", entity1);Hub远程实时通信
Install-Package
Microsoft.AspNetCore.SignalR.Client
Microsoft.AspNetCore.SignalR.Common
服务端
LmesHub类
using Lmes服务端.数据类型;
using Microsoft.AspNetCore.SignalR;
namespace Lmes服务端.Hubs
{
public class LmesHub : Hub
{
// 客户端调用此方法发送消息到服务器
public async Task SendMessage(string? user, string? message)
{
// 将消息广播给所有连接的客户端
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public async Task 请求切换信号表(string 工站, string message)//await 服务器hub.InvokeAsync("请求切换信号表", temp.工位编号, str);相对应
{
await Clients.All.SendAsync("切换信号表", 工站, message);//服务器hub.On<string, string>("切换信号表", (工站, 信号表)相对应
}
}
}program类
builder.Services.AddSignalR(options =>
{
options.EnableDetailedErrors = true; // 启用详细错误信息
});
builder.Services.AddSignalRCore();
app.MapHub<LmesHub>("/Hub");//.withUrl($"{url}/Hub")相对应//1. SignalR Hub:
//• SignalR 是一个用于在 ASP.NET Core 应用程序中实现实时 Web 功能的库。它允许服务器端代码将即时更新推送到客户端。
//• Hub 是 SignalR 中的一个核心概念,它是一个高层次的管道,客户端和服务器可以通过它进行通信。
//• MapHub<T> 方法用于将特定的 Hub 类型映射到指定的 URL 路径。
//• 在这个例子中,LmesHub 是一个自定义的 Hub 类,"/Hub" 是客户端连接到该 Hub 时使用的 URL 路径。
app.MapHub<LmesHub>("/Hub");客户端
//• HubConnection:这是 SignalR 客户端与服务器通信的核心对象。
//• HubConnectionBuilder:用于配置和创建 HubConnection 实例。
//• WithAutomaticReconnect:启用自动重连功能,当连接断开时,客户端会自动尝试重新连接。
//• WithUrl:指定 SignalR Hub 的 URL 地址。
//• Build:构建 HubConnection 实例。
private HubConnection 服务器hub;
_ = 服务器hub初始化();
服务器hub = new HubConnectionBuilder()
.WithAutomaticReconnect()
.WithUrl($"{系统参数.设置.Lmes连接参数.LMES地址}Hub") // 服务器地址
.Build();
//• On:注册一个处理器,用于处理从服务器接收的特定类型的消息。
//• ReceiveMessage:消息的名称,服务器发送的消息必须使用相同的名称。
//• (user, message):消息处理器的参数,表示消息的内容。
//• notificationManager.ShowAsync:显示通知,通知内容包括消息的标题和内容。
//回调函数
服务器hub.On<string, string>("切换信号表", (工站, 信号表) =>
{
_ = notificationManager.ShowAsync(new NotificationContent
{
Title = "收到切换信号表的请求",
Message = $"{工站}>>{信号表}",
Type = NotificationType.Information
}, areaName: "WindowArea");
日志写入.写入("收到服务器的切换信号表请求!");
try
{
var temp = JsonSerializer.Deserialize<工位信息>(信号表);
if (工位信息表 != null)
{
工位信息表[工站] = temp;
}
系统参数.信号表修改(工站, temp);
日志写入.写入("切换信号表成功!");
}
catch (Exception ex)
{
日志写入.写入("切换信号表失败!\r\n" + ex.ToString());
}
});
private async void MenuItem_信号表切换_Click(object sender, RoutedEventArgs e)
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
//设置文件过滤器
ofd.Filter = "信号表文件 (*.信号表)|*.信号表";
ofd.DefaultExt = "信号表";
//不允许多选
ofd.Multiselect = false;
//显示对话框并检查用户是否选择了文件
if (ofd.ShowDialog() == true)
{
string filePath = ofd.FileName;
var str = await File.ReadAllTextAsync(filePath);
var temp = JsonSerializer.Deserialize<工位信息>(str);
await 服务器hub.InvokeAsync("请求切换信号表", temp.工位编号, str);
}
}
catch (Exception ex)
{
await this.ShowMessageAsync("发送失败", ex.ToString());
}
}系统参数
//两个参数的事件委托
public static event Action<string, 工位信息?> 信号表修改事件;
public static void 信号表修改(string 工站, 工位信息? temp)
{
//订阅
信号表修改事件?.Invoke(工站, temp);
}
private async void MetroWindow_Loaded(object sender, RoutedEventArgs e)
{
//注册信号表更新事件
系统参数.信号表修改事件 += (工站, temp) =>
{
File.WriteAllText(Path.Combine(系统参数.配置文件路径, $"{工站}.信号表"), JsonSerializer.Serialize(temp, new JsonSerializerOptions()
{
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
WriteIndented = true,
}));
};
}在服务端,定义一个 SignalR Hub 类,并在其中定义可以被客户端调用的方法。
using Microsoft.AspNetCore.SignalR;
public class LmesHub : Hub
{
// 客户端调用此方法发送消息到服务器
public async Task SendMessage(string user, string message)
{
// 将消息广播给所有连接的客户端
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}发送消息到服务器
private async Task SendMessageToServer(string user, string message)
{
try
{
await 服务器hub.InvokeAsync("SendMessage", user, message);
}
catch (Exception ex)
{
_ = notificationManager.ShowAsync(new NotificationContent
{
Title = "发送消息失败",
Message = $"{ex}",
Type = NotificationType.Error
}, areaName: "WindowArea");
}
}线程锁定
避免死锁:
如果两个线程都在等待对方释放的锁,就会出现死锁问题。
解决办法:
尽量减少嵌套锁。
使用固定的锁定顺序。
尽量减少锁竞争:
高频访问的资源,尽量使用线程安全的数据结构(如 ConcurrentDictionary)。
wpf中嵌套数据表格
<DataGridTemplateColumn Header="子物料详情">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DataGrid x:Name="PART_DataGrid" AutoGenerateColumns="False" IsReadOnly="True" ItemsSource="{Binding data}">
<DataGrid.Columns>
<DataGridTextColumn Header="子物料追溯码" Binding="{Binding assemblyMaterialSn}" />
<DataGridTextColumn Header="子物料名称" Binding="{Binding assemblyMaterialName}" />
<DataGridTextColumn Header="子物料条码" Binding="{Binding assemblyMaterialCode}" />
<DataGridTextColumn Header="子物料数量" Binding="{Binding assemblyMaterialQty}" />
<DataGridTextColumn Header="子物料组装时间" Binding="{Binding assemblyTime}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>话说这个和前端的插槽好像,Template,嗯,就是wpf一点文档都没有。
this.ShowInputAsync和this.ShowMessageAsync
var s = await this.ShowInputAsync("请输入客户条码","请输入客户条码");
catch (Exception ex) { this.ShowMessageAsync("查询出错!可能是没有记录", ex.ToString()); }
豪看
MessageDialogResult showRe = await this.ShowMessageAsync("请确认信号表信息",
$"名称:{temp.名称}\r\n" +
$"版本:{temp.版本号}\r\n",
MessageDialogStyle.AffirmativeAndNegative,
new MetroDialogSettings()
{
AffirmativeButtonText = "确定",
NegativeButtonText = "取消"
});
if(showRe == MessageDialogResult.Affirmative)
{
读取当前信号表(temp.信号表);
}卧槽了电子大屏
在App.xaml.cs中添加
LiveChartsCore.LiveCharts.Configure(config => config.HasGlobalSKTypeface(songtiTypeface).AddDarkTheme().AddLightTheme());引用静态变量
在程序运行期间,所有引用同一个静态变量的地方都会指向相同的内存地址。这意味着:
内存位置:
静态变量只会在内存中存储一份
所有对这个静态变量的引用都指向这同一个内存位置
当一个地方修改了静态变量的值,所有引用这个静态变量的地方都会看到新的值
生命周期:
静态变量在程序启动时创建
在整个程序运行期间都存在于内存中直到程序结束才会被销毁
正是因为所有引用指向同一个地址,在多线程环境下需要特别注意线程安全问题。使用
ConcurrentDictionary等线程安全的集合:
public static ConcurrentDictionary<string, List<工站对应物料信息>> 工站物料信息 = new();如果我写的物料绑定出现错误了,
1-将静态资源 物料名称表格 改为线程安全的字典
2-添加值时,直接根据工单BOM的orderCode来添加key 和value
3-获取值时,直接复制物料名称表格[当前选择工站]就🆗
为什么要使用委托、事件
需要相同的输入签名、指向函数的指针 delegate 委托=new() 委托=method;
委托实际上就是一种回调机制,它们充当并发、异步或并行过程之间的通信渠道
多播委托 -为一个委托附加多个方法
我想通过一个服务端同时修改多个客户端的配置文件,使用 SignalR 可以显著简化实时通讯的实现。
通过 SignalR向客户端推送系统更新、警报或其他实时数据。同时实际上是异步且并发的,不会阻塞主线程。
帮助我们在多线程或并行处理中进行通信
事件是基于委托构建的,委托主要用于回调机制,它们不提供封装是公开暴露的,是委托的封装,事件不可直接赋值 事件使得委托得到了protect
发布者->订阅者1...->订阅者n
事件帮助实现纯粹的发布者-订阅者模型或观察者-可观测模型
堆栈跟踪
Environment.StackTrace
Console.WriteLine(Environment.StackTrace);堆栈跟踪是所有已调用的方法的轨迹,顶部是最近的方法,底部是最早的方法
throw保留堆栈跟踪,有助于找到问题的原始来源
throw ex 不保留
特性
所有特性都必须派生自Attribute基类
特性通常以"Attribute"结尾,实际使用时,可以省略
[StringLengthValidate(2, 25)]
public string Name { get; }
class StringLengthValidateAttribute : Attribute
{
public int Min { get; }
public int Max { get; }
public StringLengthValidateAttribute(int min, int max)
{
Min = min;
Max = max;
}
}
