新对象初始化器(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中的数据

屏幕截图 2024-12-13 090018.jpg

配置文件保存

//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());

引用静态变量

在程序运行期间,所有引用同一个静态变量的地方都会指向相同的内存地址。这意味着:

  1. 内存位置:

  • 静态变量只会在内存中存储一份

  • 所有对这个静态变量的引用都指向这同一个内存位置

  • 当一个地方修改了静态变量的值,所有引用这个静态变量的地方都会看到新的值

  1. 生命周期:

  • 静态变量在程序启动时创建

  • 在整个程序运行期间都存在于内存中

  • 直到程序结束才会被销毁

  • 正是因为所有引用指向同一个地址,在多线程环境下需要特别注意线程安全问题。

  • 使用 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;
	}
}