MES开发中, 客户往往会要求 工单开始时记录工艺数据, 工单结束时将这些工艺数据回传到更上一级的WES系统中. 因为MES系统和PLC 是多线程读取, 所以加锁, 事件触发是常用手段.
using MyWebApiTest.PLC;
using MyWebApiTest.Service;
using MyWebApiTest.Service.Entry;
using MyWebApiTest.Service.Entry.Imp;
using MyWebApiTest.Service.Factory;
using MyWebApiTest.Service.Factory.Impl;
using MyWebApiTest.Utils;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//日志
builder.Host.UseSerilog((context, logger) =>
{
logger.ReadFrom.Configuration(context.Configuration);
logger.Enrich.FromLogContext();
});
// Add services to the container.
builder.Configuration.AddJsonFile("appsettings.json", false, true);
var cfg = builder.Configuration;
builder.Services.AddSingleton<IConfiguration>(cfg);
//ORM
builder.Services.AddSingleton<ISqlSugarService ,SqlSugarServiceImpl>();
builder.Services.AddSingleton<SqlSugarHelper>();
builder.Services.AddSingleton<IAbsFactoryService, AbsFactoryServiceImpl>();
builder.Services.AddSingleton<IConnFactoryService, ConnFactoryServiceImpl2>();
builder.Services.AddSingleton<IConnFactoryService, ConnFactoryServiceImpl1>();
//PLC数据
builder.Services.AddSingleton<MyS7Entry>();
builder.Services.AddSingleton<IS7ConnService, S7ConnServiceImpl>();
//运行初始化任务 测试client
//builder.Services.AddSingleton();
//client
builder.Services.AddHttpClient();
//AutoMapper
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddControllers();
builder.Services.AddCors(c => c.AddPolicy("any", p => p.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//日志
builder.Host.UseSerilog((context, logger) =>
{
logger.ReadFrom.Configuration(context.Configuration);
logger.Enrich.FromLogContext();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors("any");
app.UseAuthorization();
app.MapControllers();
app.Run();
using S7.Net;
namespace MyWebApiTest.Service.Entry
{
public interface IS7ConnService
{
void ConnPlc();
bool MyIsConnected
{
get;
}
Plc MyS7Master { get; }
}
}
using MyWebApiTest.PLC;
using MyWebApiTest.Service.Entry;
using S7.Net;
namespace MyWebApiTest.Service.Entry.Imp
{
public class S7ConnServiceImpl : IS7ConnService
{
public S7ConnServiceImpl(IConfiguration configuration,
HttpClient httpClient,
ILogger<S7ConnServiceImpl> logger,
MyS7Entry myS7Entry)
{
this.configuration = configuration;
this.httpClient = httpClient;
this.logger = logger;
this.myS7Entry = myS7Entry;
myIp = configuration.GetSection("PlcIp").Value;
}
private readonly IConfiguration configuration;
private readonly HttpClient httpClient;
private readonly ILogger<S7ConnServiceImpl> logger;
private MyS7Entry myS7Entry;
private string myIp;
private Plc myS7Master = null;
private MyS7EntityRecive? myS7test = new MyS7EntityRecive();
private MyS7Entry myS7Data = new MyS7Entry();
private bool myIsConnected = false;
private CancellationTokenSource cts = new();
private int errorTimes = 0;
private static readonly object lockObj = new object(); // 创建一个对象作为锁
public Plc MyS7Master
{
get => myS7Master;
}
public MyS7Entry MyS7Data
{
get => myS7Data;
set => myS7Data = value;
}
public bool MyIsConnected
{
get => myIsConnected;
set
{
if (myIsConnected == false && value == true)
{
logger.LogInformation("PLC连接成功!");
}
myIsConnected = value;
}
}
public void ConnPlc()
{
Task.Run(async () =>
{
while (!cts.IsCancellationRequested)
{
if (myS7Master == null || !MyIsConnected)
{
try
{
myS7Master = new Plc(CpuType.S71500, myIp, 0, 0);
myS7Master.Open();
MyIsConnected = myS7Master.IsConnected;
}
catch (Exception ex)
{
myS7Master?.Close();
myS7Master = null;
MyIsConnected = false;
logger.LogError(ex.Message);
await Task.Delay(2000);
}
}
else if (MyIsConnected)
{
//注入Client
//var url = "http://localhost:5190/api/private/v1/My/MyGet"; // 目标 Web API 的地址
//var response = await httpClient.GetAsync(url);
//if (response.IsSuccessStatusCode)
//{
// var content = await response.Content.ReadAsStringAsync();
// logger.LogError(content);
//}
try
{
MyIsConnected = myS7Master.IsConnected;
await Task.Delay(1000);
myS7test = await myS7Master.ReadClassAsync<MyS7EntityRecive>(31, 416);
lock (lockObj)
{
myS7Entry.MyShort1 = myS7test.MyShort1;
myS7Entry.MyShort2 = myS7test.MyShort2;
}
logger.LogInformation(myS7Entry.MyShort1.ToString() + "====");
}
catch (Exception ex)
{
errorTimes++;
await Task.Delay(1000);
logger.LogError($"读取时发生错误:{ex.Message}");
logger.LogError($"读取时发生错误次数:{errorTimes}");
myS7Master.Close();
MyIsConnected = false;
myS7Master = null;
}
}
}
}, cts.Token);
}
}
}
/*
使用了 lock 来保护 myS7Entry.MyShort1 和 myS7Entry.MyShort2 的同时修改,
以确保在同一时间只有一个线程可以修改这两个属性。这是一种常见的使用锁的方式,
目的是避免竞态条件和数据不一致性。
死锁通常发生在两个或多个线程之间存在循环依赖锁的情况下,
导致它们互相等待对方释放锁。在你的代码中,只有一个 lock,并且在修改属性时使用,
不会导致循环依赖锁,因此不会发生死锁。
但要注意,死锁可能在其他情况下发生,比如在涉及多个锁的复杂情况下,
或者在锁嵌套的情况下。确保你的代码中不会出现多个锁之间的循环依赖,
以及在锁内部避免阻塞线程,以保证代码的正常执行。
*/
using System.ComponentModel;
namespace MyWebApiTest.PLC
{
public class MyS7Entry : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private short myShort1;
public short MyShort1
{
get => myShort1;
set
{
if (myShort1 != value)
{
if (myShort1 == 1 && value == 0)
{
OnPropertyChanged(nameof(MyShort1));
}
myShort1 = value;
}
}
}
private short myShort2;
public short MyShort2
{
get => myShort2;
set
{
if (myShort2 != value)
{
if (myShort2 == 1 && value == 0)
{
OnPropertyChanged(nameof(MyShort2));
}
myShort2 = value;
}
}
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
namespace MyWebApiTest.PLC
{
public class MyS7EntityRecive
{
public short MyShort1 { get; set; }
public short MyShort2 { get; set; }
}
}
using MyWebApiTest.entities;
using MyWebApiTest.PLC;
using MyWebApiTest.Service.Entry;
using System.Text.Json;
namespace MyWebApiTest.Utils
{
public class StartupInitializationService : IHostedService
{
private readonly MyS7Entry myS7Entry;
private readonly IS7ConnService s7ConnService;
private readonly ILogger<StartupInitializationService> logger;
private readonly HttpClient httpClient;
public StartupInitializationService(MyS7Entry myS7Entry
, IS7ConnService s7ConnService
, ILogger<StartupInitializationService> logger
, HttpClient httpClient
)
{
this.myS7Entry = myS7Entry;
this.s7ConnService = s7ConnService;
this.logger = logger;
this.httpClient = httpClient;
}
public Task StartAsync(CancellationToken cancellationToken)
{
try
{
s7ConnService.ConnPlc();
}
catch (Exception ex)
{
logger.LogError($"网络错误:{ex.Message}");
return Task.CompletedTask;
}
logger.LogWarning("初始化函数成功");
myS7Entry.PropertyChanged += async (s, e) =>
{
if (e.PropertyName == "MyShort1")
{
string? url = "http://127.0.0.1:8081/endpoint/mes/kx/reportA";
Student stu = new()
{
Id = 1,
Name = "MyBool1",
Age = 999
};
var json = JsonSerializer.Serialize(stu);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
logger.LogInformation("Data sent successfullyS1.");
}
else
{
logger.LogInformation("Failed to send dataS1.");
}
}
else if (e.PropertyName == "MyShort2")
{
string? url = "http://127.0.0.1:8081/endpoint/mes/kx/reportB";
Student stu = new()
{
Id = 2,
Name = "MyBool2",
Age = 888
};
var json = JsonSerializer.Serialize(stu);
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
logger.LogInformation("Data sent successfullyS1.");
}
else
{
logger.LogInformation("Failed to send dataS1.");
}
}
};
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
// 在应用程序停止时执行清理逻辑(如果有必要)
return Task.CompletedTask;
}
}
}