編程就是不斷寫BUG的過程,許多東西並不是不懂,但是有時候就是想不起來,看不出來,改不過來。
今天在使用foreach的時候,程序報了一段錯誤,大概意思就是遍歷器出了毛病,到網上查了查,原來是因爲Foreach的只讀性,在Foreach中是不能對迭代變量item就行修改的,只需要把foreach改用for循環就OK。
第二個問題就麻煩了直接上代碼
public List<RoomMemberInfo> rmiList = new List<RoomMemberInfo>();
public Action<List<RoomMemberInfo>> ReceiveAction;
public void BroadRoomInfo()
{
foreach (var item in ClientList)
{
RoomMemberInfo info = new RoomMemberInfo()
{
id = item.Key,
name = item.Value.username,
otherInfo = ""
};
rmiList.Add(info);
}
ReceiveAction(rmiList);
string json = JsonMapper.ToJson(rmiList);
rmiList.Clear();
BroadCast(json);
}
這時最近自己在寫一個類似CS的局域網聯機大廳的一段用於廣播的代碼,其中的rmiList是服務端上存儲的房間信息,由於廣播方法實在每次收到新的客戶端連接時調用的,所以我在每次廣播後對rmiList進行了Clear()但是當我在服務端去用信息更新面板的時候問題發生了。
List<RoomMemberInfo> list = new List<RoomMemberInfo>();
void Start()
{
EthernetManager.instance.ReceiveAction += UpdateRoomPanelInfo;
}
public void UpdateRoomPanelInfo(List<RoomMemberInfo> list)
{
this.list = list;
}
void Update()
{
if (list.Count == 0)
return;
roomNumber.text = list.Count.ToString() + "/" + "8";
for (int i = 0; i < list.Count; i++)
{
for (int j = 0; j < content.childCount; j++)
{
Destroy(content.GetChild(i).gameObject);
}
PlayerUI playerUI = Instantiate(Resources.Load<PlayerUI>("Prefabs/playerUI"));
playerUI.transform.SetParent(content, false);
playerUI.Init(list[i]);
}
list.Clear();
}
由於調用廣播是在一個用於監聽的線程中調用,Unity不允許在線程中操作組件,所以我在事件中註冊了UpdateRoomPanelnfo方法,把list值賦給了類的一個成員變量,然後在Update中進行了檢測,如果有值則執行後面的方法,這時候問題來了,在我進行測試的時候有時候會執行成功實例化出PlayerUI,有時候就會各種報錯,有的時候既不報錯也不執行,在打斷點調試的時候又基本都能成功。
每次編程的時候都會感覺直接報錯或者出現明顯的運行錯誤都好處理,只有這種有時候沒問題有時候又出問題的是最難處理。
經過幾個小時的反覆調試,最後突然靈光一閃,發現自己煩了一個基礎的引用傳遞問題,如果List中存儲的是對象,List在傳遞的時候就是一個引用的地址傳遞,所以在上面代碼中UpdateRoomPanelInfo()方法執行後,list就是rmiList,所以如果Update執行一次的時間長於BroadCast方法中執行到Clear()的時間,list就會從有變無,導致update判斷失敗,這也是爲什麼在打斷點測試的時候成功率很高因爲打斷點可以讓後者執行完再執行前者。
找到了問題的關鍵在於List傳遞了引用而不是值,一種解決方案是採用深度複製,而不是直接傳遞,方法這位博主講的很清楚了。
http://blog.csdn.net/xjavasunjava/article/details/7648242
我則是採取了第二種取巧的方案,直接
ReceiveAction(rmiList);
string json = JsonMapper.ToJson(rmiList);
Thread.Sleep(2000);
rmiList.Clear();
等待2s,在執行Clear這時候該實例化的對象都搞定了,在執行清空操作就沒問題了,當然要記得去掉後面的list.Clear()
剛發現只需要把rmiList.Clear()這句直接刪掉,前面的方法都走了彎路,寫代碼寫久了是真的犯蠢