多人点餐小程序,同一桌的顾客可以看见彼此所点的菜品,某一顾客点击提交订单过程中不允许其他顾客进行点餐操作,防止订单提交前后点餐数据不一致。即提交订单的过程中锁定订单,顾客确认或取消订单后解除锁定。
一、AutoResetEvent的基本使用
微软官方文档地址:
1、AutoResetEvent的构造方法
public AutoResetEvent (bool initialState);
构造方法参数 initialState
true:创建实例对象,并设置对象的信号量为true。
false:创建实例对象,并设置对象的信号量为false。
AutoResetEvent autoResetEvent = new AutoResetEvent(true);
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
2、AutoRessetEvent的方法
(1)WaitOne()
监听autoResetEvent对象的信号量,若信号量的值为true,WaitOne()方法返回true,线程继续执行;若autoResetEvent对象的信号量为false,WaitOne()方法不返回,等待信号量变为true,等待过程中阻塞当前线程。
(2)WaitOne(int milliseconds)
在指定的毫秒内监听autoResetEvent对象的信号量,指定时间内autoResetEvent对象的信号量为变为true,WaitOne方法返回true,线程继续执行。若指定时间内等待autoResetEvent对象的信号量没有变为true,WaitOne方法返回false,线程不再监听信号量,不会阻塞。
(3)Set()
将autoResetEvent对象的信号量变为true。
(4)Reset()
将autoResetEvent对象的信号量变为false。
3、对AutoResetEvent的理解
(1)AutoResetEvent的实例对象拥有一个信号量属性,该属性相当于一个红绿灯。信号量==true时绿灯亮起,信号量==false时红灯亮起。线程调用WaitOne()方法监听红绿灯,红等亮起时所有线程阻塞;绿灯亮起时只有一条线程能通过,该线程通过后AutoResetEvent会自动调用Reset方法将信号量变为false即亮起红灯,剩下的线程阻塞。
(2)线程之间监听同一个红绿灯的线程是竞争关系。即调用同一个AutoResetEvent实例对象的WaitOne()方法的线程是竞争关系。
(3)线程之间监听不同红绿灯的线程不是竞争关系,即调用不同AutoResetEvent实例对象的WaitOne()方法的线程不是竞争关系。
4、AutoResetEvent案例
(1)构造方法为参数为true时,创建对象并将信号量设为true。第1个WaitOne()方法返回true;
第2个WaitOne()方法返回false。
//创建实例对象并将信号量设为true
AutoResetEvent autoResetEvent = new AutoResetEvent(true);
bool isGreenLight1 = autoResetEvent.WaitOne(1000);//1秒内信号量是否为true
Console.WriteLine(isGreenLight1);
//第1个WaitOne方法执行成功后,autoResetEvent自动调用Reset()方法将信号量改为false
bool isGreenLight2 = autoResetEvent.WaitOne(1000);//1秒内信号量是否为true
Console.WriteLine(isGreenLight2);
/*
结果:
isGreenLight1 == True;
isGreenLight2 == False;
*/
(2)构造方法为参数为false时,创建对象并将信号量设为false,所有的WaitOne()方法都返回false。
//创建实例对象并将信号量设为false
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
bool isGreenLight1 = autoResetEvent.WaitOne(1000);//1秒内信号量是否为true
Console.WriteLine(isGreenLight1);
bool isGreenLight2 = autoResetEvent.WaitOne(1000);//1秒内信号量是否为true
Console.WriteLine(isGreenLight2);
/*
结果:
isGreenLight1 == False;
isGreenLight2 == False;
*/
(3)构造方法为参数为false时,创建对象并将信号量设为false。调用Set()方法将信号量改为true,第1个WaitOne()方法返回true。
//创建实例对象并将信号量设为true
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
autoResetEvent.Set();//将信号量改为true
bool isGreenLight1 = autoResetEvent.WaitOne(1000);//1秒内信号量是否为true
Console.WriteLine(isGreenLight2);
//第1个WaitOne方法执行成功后,autoResetEvent自动调用Reset()方法将信号量改为false
bool isGreenLight2 = autoResetEvent.WaitOne(1000);//1秒内信号量是否为true
Console.WriteLine(isGreenLight2);
/*
结果:
isGreenLight1 == True;
isGreenLight2 == False;
*/
二、本项目中使用AutoResetEvent
1、在program.cs文件中注册一个单例的字典,单例保证所有客户端访问到同一个字典,字典的key为string类型的餐桌编号,value为AutoResetEvent对象。此数据结构保证同一餐桌的顾客调用同一个AutoResetEvent实例,不同餐桌的顾客互不影响。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddSingleton<Dictionary<string,AutoResetEvent>>();
2、在Controller中依赖注入该字典。
public class WX_OrderController : ControllerBase
{
private Dictionary<string, AutoResetEvent> autoResetEventDic;
public WX_OrderController(Dictionary<string, AutoResetEvent> autoResetEventDic)
{
this.autoResetEventDic = autoResetEventDic;
}
}
3、点餐-添加菜品 接口如下:客户端发起的第一条点餐请求将会创建AutoResetEvent实例对象,并将该对象添加入字典中,以餐桌编号为key。实例对象autoResetEvent的信号初始值为true,当前线程调用WaitOne方法返回true,线程继续执行;其余线程必须等待。防止多顾客点餐导致订单数据不一致。
注意:接口retrun之前需要调用autoResetEvent的Set()方法,将信号量改为true,否则会导致订单锁定。
public IActionResult AddOrderDetail(WXOrderDetailVo wXOrderDetailVo)
{
AutoResetEvent? autoResetEvent;
if (!autoResetEventDic.TryGetValue(wXOrderDetailVo.TableNo, out autoResetEvent)){
autoResetEventDic.Add(wXOrderDetailVo.TableNo,new AutoResetEvent(true));
}
bool isGreenLight = autoResetEventDic[tableNo].WaitOne(TimeSpan.FromSeconds(1));
if (!isGreenLight ){
return BadRequest("订单提交中,请勿点餐");
}
else
{
/*执行添加菜品操作*/
autoResetEventDic[tableNo].Set();
}
retrun Ok("菜品添加成功");
}
备注:传入参数wXOrderDetailVo包含了餐桌信息—TableNo
4、客户端点击 ”现在下单“ 按钮 发送锁定订单请求,该接口调用对应餐桌的AutoRestEvent对象的WaitOne()方法,WaitOne()方法成功调用后AutoRestEvent对象会自动调用Reset()方法将信号量改为false,此时订单锁定无法再点餐。
[HttpGet]
public IActionResult LockOrder(string tableNo)
{
if (string.IsNullOrEmpty(tableNo)) return BadRequest("tableNo不能为空");
AutoResetEvent? autoResetEvent;
if (!autoResetEventDic.TryGetValue(tableNo, out autoResetEvent))
{
autoResetEventDic.Add(tableNo, new AutoResetEvent(true));
}
autoResetEventDic[tableNo].WaitOne();
return Ok("订单锁定中");
}
5、客户端确定或取消提交订单都会调用UnLockOrder接口,将对应餐桌的AutoRestEvent对象的信号量改为true,此时订单解除锁定可以继续点餐。
[HttpGet]
public IActionResult UnLockOrder(string tableNo)
{
AutoResetEvent? autoResetEvent;
if (!autoResetEventDic.TryGetValue(tableNo, out autoResetEvent))
return Ok($"{tableNo}订单未锁定,不需要解锁");
bool UnLockResult = autoResetEvent.Set();
if (!UnLockResult) return BadRequest($"解除锁定{tableNo}的订单失败");
return Ok($"Unlock {tableNo}订单成功");
}
三、补充说明ManulRestEvent
用法与AutoManulEvent类似,但调用Waitone()方法后不会自动调用Reset()方法。
//创建实例对象并将信号量设为false
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
manualResetEvent.Set();
//调用Set()方法后将信号量改为true,后续的WaitOne()方法监听到的信号量一直为true
bool isGreenLight1 = manualResetEvent.WaitOne(1000);//1秒内信号量是否为true
Console.WriteLine(isGreenLight1);
bool isGreenLight2 = manualResetEvent.WaitOne(500);//0.5秒内信号量是否为true
Console.WriteLine(isGreenLight2);
/*
结果:
isGreenLight1 == True;
isGreenLight2 == True;
*/