電梯多線程調度——最終版詳細設計
——小組成員:何曉楠,劉鑄輝
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 |