【结对项目开发】电梯多线程调度——最终版
电梯多线程调度——最终版详细设计
——小组成员:何晓楠,刘铸辉
1.时间进度表
时间 |
3.10-3.12 |
3.12 |
3.13 |
3.14上午 |
3.14下午 |
3.15上午 |
3.16上午 |
3.16下午 |
3.17 |
3.18-3.19 |
3.20 |
何晓楠 |
通过查阅资料和视频学习C#界面制作和多线程的处理 |
编写elevator类和 ElevatorController类 |
设计电梯界面 |
研究多线程的实例,编写电梯开关门,内部外部按钮事件 |
把我们做的代码和界面进行整合 |
讨论电梯调度的优化问题,并进行单元测试,设置测试用例 |
界面的完善 |
完成alpha版 |
和其他组讨论载重限制问题,并尝试我们的设想 |
载重与出入功能的界面设计 |
编写载重功能测试用例 |
刘铸辉 |
添加事件代码 |
界面的设计与修改 |
优化部分方法 |
编写出入人数和载重限制的代码 |
完成最终版 |
经过几天的奋战,我们终于完成了最终版。
2.电梯说明
1.电梯内部视图 1~20为相应的楼层。 按下即可响应相应楼层,按下后变成红色即被响应;
2.最右边是楼层示意图,当电梯到达相应楼层后,红色的响应就会取消,会有关门图片变为开门图片;
3.中间外部的38个按钮(4部电梯共享此按钮),△表示向上请求,▽表示向下请求;
4.最上面的电梯运行参数设置,是设置每台电梯运行速度,载重限制,上下人数是否正常运行等参数的;
下面是整个界面:
3.程序设计图
3.1电梯状态转移图
4.程序关键实现思想
4.1多线程调度
在这里我们用的C#多线程Thread方法进行调度。下面用具体过程介绍在电梯中C#多线程的用法。
当外部请求按下4楼向上的请求时
1。程序首先初始化所有按钮及四部电梯,然后调用电梯调度类ElevatorController中upOrDown方法,判断是内部请求还是外部请求,以及是上楼还是下楼,此时是外部,上请求然后当前面板被显示为红色。
2.此时判断电梯是否在执行任务,发现电梯1未执行任务,初始化多线程Thread类,调用ThreadStart方法启动线程1。关键代码如下:
1 /// <summary> 2 /// 操作电梯判断 3 /// </summary> 4 /// <param name="BiaoJi">标记选择</param> 5 /// <param name="type">响应类型</param> 6 /// <param name="select">选择哪一个类型</param> 7 public void upOrDown(int BiaoJi, int type, int select) 8 { 9 if (BiaoJi == INTERIOR) 10 { 11 if (type == ELE1) 12 { 13 ele_1.setPanel(select); 14 } 15 else if (type == ELE2) 16 { 17 ele_2.setPanel(select); 18 } 19 else if (type == ELE3) 20 { 21 ele_3.setPanel(select); 22 } 23 else if (type == ELE4) 24 { 25 ele_4.setPanel(select); 26 } 27 else 28 { 29 Exception ex = new Exception("电梯类型出错!"); 30 throw ex; 31 } 32 } 33 else if (BiaoJi == EXTERIOR) 34 { 35 if (type == UP) 36 { 37 uppanel[select] = true; 38 } 39 else if (type == DOWN) 40 { 41 downpanel[select] = true; 42 } 43 else 44 { 45 Exception ex = new Exception("EXTERIOR type 错误"); 46 throw ex; 47 } 48 } 49 else 50 { 51 Exception ex = new Exception("BiaoJi错误"); 52 throw ex; 53 } 54 55 //判断电梯是否在执行任务 56 if (!ele_1.nowrun) 57 { 58 ele_1.nowrun = true; 59 Thread th1 = new Thread(new ThreadStart(run_ele1)); 60 th1.IsBackground = true; 61 th1.Start(); 62 } 63 else if (!ele_2.nowrun) 64 { 65 ele_2.nowrun = true; 66 Thread th2 = new Thread(new ThreadStart(run_ele2)); 67 th2.IsBackground = true; 68 th2.Start(); 69 } 70 else if (!ele_3.nowrun) 71 { 72 ele_3.nowrun = true; 73 Thread th3 = new Thread(new ThreadStart(run_ele3)); 74 th3.IsBackground = true; 75 th3.Start(); 76 } 77 else if (!ele_4.nowrun) 78 { 79 ele_4.nowrun = true; 80 Thread th4 = new Thread(new ThreadStart(run_ele4)); 81 th4.IsBackground = true; 82 th4.Start(); 83 } 84 }
3.调用电梯运行run方法,通过调用isGoOn方法判断是当前哪一层请求,再通过OpenDoor方法判断是否应该带用开门动画,最后将外部请求require通过Getrequire方法返回给run方法处理,上升则ele.floor++,下降则ele.floor--。关键方法代码如下:
1 /// <summary> 2 /// 运行电梯 3 /// </summary> 4 /// <param name="ele">被调度的电梯</param> 5 public void run(Elevator ele) 6 { 7 for (; arrive(ele); ) 8 { 9 for (OpenDoor(ele); ele.gatestatus == OPEN; OpenDoor(ele)) 10 { 11 Thread.Sleep(3000); 12 ele.gatestatus = CLOSE; 13 } 14 int require = NONE; 15 require = Getrequire(ele); 16 if (require == MOVEUP) 17 { 18 ele.floor += 1; 19 if (!floorJudge(ele)) 20 { 21 Exception ex = new Exception("楼层错误"); 22 throw ex; 23 } 24 Thread.Sleep(1000); 25 } 26 else if (require == MOVEDOWN) 27 { 28 ele.floor -= 1; 29 if (!floorJudge(ele)) 30 { 31 Exception ex = new Exception("楼层错误"); 32 throw ex; 33 } 34 Thread.Sleep(1000); 35 } 36 else if (require == NONE) 37 { 38 if (!floorJudge(ele)) 39 { 40 Exception ex = new Exception("楼层错误"); 41 throw ex; 42 } 43 } 44 else 45 { 46 Exception ex = new Exception("获取的任务出错"); 47 throw ex; 48 } 49 } 50 ele.nowrun = false; 51 } 52 53 /// <summary> 54 /// 响应向上和向下获取用户请求判断 55 /// </summary> 56 /// <param name="ele">被调度的电梯</param> 57 /// <returns>电梯运行状态</returns> 58 private int Getrequire(Elevator ele) 59 { 60 if (ele.direction == UP)//方向向上获取任务 61 { 62 if (UpAsk(ele)) 63 { 64 return MOVEUP; 65 } 66 if (DownAsk(ele)) 67 { 68 return MOVEDOWN; 69 } 70 } 71 else if (ele.direction == DOWN)//方向向下获取任务 72 { 73 if (DownAsk(ele)) 74 { 75 return MOVEDOWN; 76 } 77 if (UpAsk(ele)) 78 { 79 return MOVEUP; 80 } 81 } 82 else 83 { 84 Exception ex = new Exception("电梯状态出错!"); 85 throw ex; 86 } 87 return NONE; 88 }
4.调用电梯是否开门OpenDoor方法时,需要调用UpAsk和DownAsk方法来查询是否有向上或向下的乘客同时请求并用线程调度优先级最高的去接乘客。关键代码如下:
1 /// <summary> 2 /// 是否开门操作 3 /// </summary> 4 /// <param name="ele"></param> 5 private void OpenDoor(Elevator ele) 6 { 7 if (ele.direction == UP) 8 { 9 if (ele.panel[ele.floor] || uppanel[ele.floor]) 10 { 11 ele.gatestatus = OPEN; 12 ele.panel[ele.floor] = false; 13 uppanel[ele.floor] = false; 14 return; 15 } 16 if (!UpAsk(ele)) 17 { 18 if (downpanel[ele.floor]) 19 { 20 ele.gatestatus = OPEN; 21 ele.direction = DOWN; 22 downpanel[ele.floor] = false; 23 return; 24 } 25 } 26 } 27 else if (ele.direction == DOWN) 28 { 29 if (ele.panel[ele.floor] || downpanel[ele.floor]) 30 { 31 ele.gatestatus = OPEN; 32 ele.panel[ele.floor] = false; 33 downpanel[ele.floor] = false; 34 return; 35 } 36 if (!DownAsk(ele)) 37 { 38 if (uppanel[ele.floor]) 39 { 40 ele.gatestatus = OPEN; 41 ele.direction = UP; 42 uppanel[ele.floor] = false; 43 return; 44 } 45 } 46 } 47 else 48 { 49 Exception ex = new Exception("电梯状态出错"); 50 throw ex; 51 } 52 }
5.此时如果又有其他外部请求同时按下,或者再加上内部请求,此时系统会调用电梯调度类ElevatorController中upOrDown方法以及UI线程完成窗体按钮的显示和4部电梯的最短路径调度。在这里只是用了UI线程响应窗体按钮事件以及电梯上下楼线程调度,主要是UI线程启动一个消息循环,每次从本线程所对应的消息队列中取出一条消息,然后根据消息所包容的信息,将其转发给特定的窗体对象,此窗体对象所对应的“窗体过程”函数被调用以处理这些消息。
1 public void UIController()//UI控制线程 2 { 3 try 4 { 5 for (; true; ) 6 { 7 #region 电梯面板灯 8 for (int i = 0; i < 20; ++i) 9 { 10 //上升按钮 11 if (myElevator.uppanel[i]) 12 { 13 btnUpPanel[i].BackColor = Color.Red; 14 } 15 if (!myElevator.uppanel[i]) 16 { 17 btnUpPanel[i].BackColor = Color.White; 18 } 19 //下降按钮 20 if (myElevator.downpanel[i]) 21 { 22 btnDownPanel[i].BackColor = Color.Red; 23 } 24 if (!myElevator.downpanel[i]) 25 { 26 btnDownPanel[i].BackColor = Color.White; 27 } 28 //电梯1 29 if (myElevator.ele_1.panel[i]) 30 { 31 btnEle1[i].BackColor = Color.Red; 32 } 33 if (!myElevator.ele_1.panel[i]) 34 { 35 btnEle1[i].BackColor = Color.White; 36 } 37 //电梯2 38 if (myElevator.ele_2.panel[i]) 39 { 40 btnEle2[i].BackColor = Color.Red; 41 } 42 if (!myElevator.ele_2.panel[i]) 43 { 44 btnEle2[i].BackColor = Color.White; 45 } 46 //电梯3 47 if (myElevator.ele_3.panel[i]) 48 { 49 btnEle3[i].BackColor = Color.Red; 50 } 51 if (!myElevator.ele_3.panel[i]) 52 { 53 btnEle3[i].BackColor = Color.White; 54 } 55 //电梯4 56 if (myElevator.ele_4.panel[i]) 57 { 58 btnEle4[i].BackColor = Color.Red; 59 } 60 if (!myElevator.ele_4.panel[i]) 61 { 62 btnEle4[i].BackColor = Color.White; 63 } 64 } 65 #endregion 66 67 label_ele1.Text = "Ele1.floor:" + (myElevator.ele_1.floor + 1).ToString(); 68 label_ele2.Text = "Ele2.floor:" + (myElevator.ele_2.floor + 1).ToString(); 69 label_ele3.Text = "Ele3.floor:" + (myElevator.ele_3.floor + 1).ToString(); 70 label_ele4.Text = "Ele4.floor:" + (myElevator.ele_4.floor + 1).ToString(); 71 72 73 picture_ele1.Location = new Point(picture_ele1.Location.X, 622 - (30 * myElevator.ele_1.floor)); 74 picture_ele2.Location = new Point(picture_ele2.Location.X, 622 - (30 * myElevator.ele_2.floor)); 75 picture_ele3.Location = new Point(picture_ele3.Location.X, 622 - (30 * myElevator.ele_3.floor)); 76 picture_ele4.Location = new Point(picture_ele4.Location.X, 622 - (30 * myElevator.ele_4.floor)); 77 78 if (myElevator.ele_1.gatestatus == CLOSE) 79 { 80 picture_ele1.Image = imgEleClose; 81 } 82 if (myElevator.ele_1.gatestatus == OPEN) 83 { 84 picture_ele1.Image = imgEleOpen; 85 } 86 87 if (myElevator.ele_2.gatestatus == CLOSE) 88 { 89 picture_ele2.Image = imgEleClose; 90 } 91 if (myElevator.ele_2.gatestatus == OPEN) 92 { 93 picture_ele2.Image = imgEleOpen; 94 } 95 96 if (myElevator.ele_3.gatestatus == CLOSE) 97 { 98 picture_ele3.Image = imgEleClose; 99 } 100 if (myElevator.ele_3.gatestatus == OPEN) 101 { 102 picture_ele3.Image = imgEleOpen; 103 } 104 105 if (myElevator.ele_4.gatestatus == CLOSE) 106 { 107 picture_ele4.Image = imgEleClose; 108 } 109 if (myElevator.ele_4.gatestatus == OPEN) 110 { 111 picture_ele4.Image = imgEleOpen; 112 } 113 114 Thread.Sleep(100); 115 } 116 } 117 catch (Exception ex) 118 { 119 MessageBox.Show(ex.Message); 120 } 121 }
4.2关于电梯超重判断()
private void nei_renshuqueding_Click(object sender, EventArgs e) { if (nei_jinrurenshu.Text == "") { MessageBox.Show("进入人数不能为空"); } else { if (nei_likairen.Text == "") { MessageBox.Show("离开人数不能为空!"); } else { if (Convert.ToInt32(nei_jinrurenshu.Text) < Convert.ToInt32(nei_likairen.Text)) { MessageBox.Show("进出人数有误,请查看是否有幽灵存在!"); } else { renshu2 = Convert.ToInt32(nei_jinrurenshu.Text) - Convert.ToInt32(nei_likairen.Text); MessageBox.Show("人数已确定"); guanli_renshu.Text = Convert.ToString(renshu2); } } } }
电梯进出人数的判断和载重设置实现的不是很好,在设置标记位的地方总是会抛出异常,没办法像现实生活那样真实。
5.电梯多线程调度测试用例
5.1电梯调度
1、电梯内部按钮:
1〉优先级测试(向上运行)
当前电梯处于向上运行状态,暂停在4楼。
输入数据:5 3
预期结果:电梯应当首先响应5楼请求,运行到5楼后再响应3楼请求,然后运行到3楼;
实际结果:电梯首先响应5楼请求,运行到5楼后再响应3楼请求,然后运行到3楼;
分 析:向上运行时 ,符合实际的逻辑,程序 能正确判断优先级,合格
2〉优先级测试(向下运行)
当前电梯处于向下运行状态,暂停在4楼。
输入数据:5 3
预期结果:电梯应当首先响应3楼请求,运行到3楼后再响应5楼请求,然后运行到5楼;
实际结果:电梯首先响应3楼请求,运行到3楼后再响应5楼请求,然后运行到5楼;
分 析:向下运行时 ,符合实际的逻辑,程序能正确判断优先级,合格
3〉当前电梯处于3楼(处于静止无方向)
输入数据:2 4
预期结果:电梯应当首先响应2楼请求,运行到2楼后再响应4楼请求,然后运行到4楼;
实际结果:电梯首先响应2楼请求,运行到2楼后再响应4楼求;
分 析: 程序判断正确,符合实际的逻辑,合格。
4〉压力测试
当在某一时刻电梯用户突然增多,短时间内出现大量请求时程否 正确处理。
输入数据:98786543
预期结果:电梯应当根据当前运行状态(向上或向下)来判断数据的优先级,按优先级高低排序,首先响应优先级高的请求;
假设1:电梯向上运行,暂停在4楼,电梯应当先依次响应 5 ﹑6﹑7﹑8﹑9楼的请求,然后在依次响应3﹑2楼的请求;
假设2:电梯向下运行,暂停在4楼,电梯应当先依次响应 3、2楼的请求,然后在依次响应5 ﹑6﹑7﹑8﹑9楼的请求;
实际结果:与假设结果相同,能够正确判断优先级。因为1层不能向下,20层不能向下,所以一层没有向下按钮,20层没有向上按钮。
电梯 |
运行状态 |
静止楼层 |
输入楼层 |
预期结果 |
实际结果 |
是否一致 |
D1 |
向上 |
4 |
5 3 |
5 3 |
5 3 |
Y |
向下 |
4 |
5 3 |
3 5 |
3 5 |
Y |
|
静止 |
4 |
5 3 |
5 3 |
5 3 |
Y |
|
D2 |
向上 |
7 |
9 10 |
9 10 |
9 10 |
Y |
5 3 |
5 3 |
5 3 |
Y |
|||
5 9 |
9 5 |
9 5 |
Y |
|||
向下 |
7 |
9 10 |
9 10 |
9 10 |
Y |
|
5 3 |
5 3 |
5 3 |
Y |
|||
5 9 |
9 5 |
5 9 |
Y |
|||
静止 |
7 |
5 9 |
9 5 |
9 5 |
Y |
|
D3 |
向上 |
10 |
9 10 |
9 10 |
9 10 |
Y |
5 3 |
5 3 |
5 3 |
Y |
|||
5 9 |
9 5 |
9 5 |
Y |
|||
向下 |
10 |
9 10 |
9 10 |
9 10 |
Y |
|
5 3 |
5 3 |
5 3 |
Y |
|||
5 9 |
9 5 |
5 9 |
Y |
|||
静止 |
10 |
5 9 |
9 5 |
9 5 |
Y |
|
D4 |
向上 |
15 |
9 10 |
9 10 |
9 10 |
Y |
5 3 |
5 3 |
5 3 |
Y |
|||
5 9 |
9 5 |
9 5 |
Y |
|||
向下 |
15 |
9 10 |
9 10 |
9 10 |
Y |
|
5 3 |
5 3 |
5 3 |
Y |
|||
5 9 |
9 5 |
5 9 |
Y |
|||
静止 |
15 |
5 9 |
9 5 |
9 5 |
Y |
2、电梯外部按钮:
因为1层不能向下,20层不能向下,所以一层没有向下按钮,
1〉当四部电梯全部静止时;
在任何一层发出向上或向下命令时:①最短距离;②从左到右;根据这两个条件的先后顺序来调用最适合的电梯。
所在楼层 |
D1 |
D2 |
D3 |
D4 |
预计结果 |
实际结果 |
是否一致 |
5 |
1 |
1 |
1 |
1 |
D1 |
D1 |
Y |
9 |
1 |
2 |
7 |
15 |
D3 |
D3 |
Y |
15 |
4 |
16 |
5 |
20 |
D2 |
D2 |
Y |
20 |
2 |
7 |
4 |
11 |
D4 |
D4 |
Y |
2〉当电梯有静止有运转时;
在任何一层发出向上或向下的命令时:①判断方向是否一致或静止;②最短距离;③从左到右;根据这三个条件的先后顺序来调用最适合的电梯。
(1)方向一致;
所在楼层 |
D1 |
D2 |
D3 |
D4 |
方向指令 |
预计结果 |
实际结果 |
是否一致 |
2 |
1 |
1 |
1 |
1 |
U |
D1 |
D1 |
Y |
D |
D1 |
D1 |
Y |
|||||
5 |
1U |
2U |
7D |
15U |
U |
D2 |
D2 |
Y |
D |
D3 |
D3 |
Y |
|||||
9 |
4U |
16D |
5D |
20D |
U |
D1 |
D1 |
Y |
D |
D2 |
D2 |
Y |
|||||
17 |
2D |
7U |
4U |
18D |
U |
D2 |
D2 |
Y |
D |
D4 |
D4 |
Y |
注:在任何一层发出向上或向下的命令时:①判断方向是否一致或静止;②最短距离;
向上时(U):所在楼层比电梯所在楼层低时,调用同向相对最短距离且电梯所在楼层高与人所在楼层的电梯或不同向的最短距离电梯;
向下时(D):同上原理
所在楼层 |
D1 |
D2 |
D3 |
D4 |
方向指令 |
预计结果 |
实际结果 |
是否一致 |
17 |
2D |
7D |
14D |
18U |
U |
D3 |
D3 |
Y |
17 |
2D |
7D |
14U |
18U |
U |
D3 |
D3 |
Y |
14 |
1D |
2D |
7D |
15U |
U |
D3 |
D3 |
Y |
二、电梯人数
初始人数为A,最初进入电梯人数A1大于等于第一次出电梯人数B1 ,之后电梯中的人数为A2:
B1<A
A2=A+(A1-B1 ); A3=A+(A2-B2 ); ... ... An=A+(An-1 - Bn-1 );
电梯 |
初始人数 |
进入人数 |
出来人数 |
预计电梯中人数 |
实际电梯中人数 |
是否一直 |
D1(10) |
0 |
5 |
0 |
5 |
5 |
Y |
5 |
4 |
6 |
3 |
ERROR |
N |
|
D2(10) |
5 |
3 |
4 |
4 |
4 |
Y |
D3(20) |
8 |
5 |
3 |
10 |
10 |
Y |
D4(20) |
9 |
4 |
1 |
12 |
ERROR |
N |