Async Extensions
Realtime API 的非同步擴展功能是建立在已知的回調模式之上的可選模型,並已添加到 .Net Realtime SDKs v5 及以上版本中。
這些非同步方法基於 C# 的 async/await 和 Tasks (TAP),Unity 已支持一段時間(更多資訊請參考 Microsoft 的 TAP 文檔)。
這些非同步 API 的實現並非針對性能優化,而是為了易用性。由於它們不涉及遊戲循環(例如 Quantum 模擬),這應該是可行的。
建立連接和發送操作本身是非同步過程,基於 Task 的非同步模式能產生更易讀和易維護的代碼。
C#
var appSettings = new new AppSettings();
var client = new RealtimeClient();
await client.ConnectUsingSettingsAsync(appSettings);
var joinRandomRoomParams = new JoinRandomRoomArgs();
var enterRoomArgs = new EnterRoomArgs();
var result = await client.JoinRandomOrCreateRoomAsync(joinRandomRoomParams, enterRoomArgs);
大多數 Realtime API 的非同步版本已經實現。如果缺少某些功能,可以輕鬆在本地添加或向 Photon 團隊提出請求。相關代碼可以在AsyncExtensions.cs
文件中找到。
所有非同步方法在被等待之前不會開始處理或發送。
在等待非同步操作方法完成時,無需更新RealtimeClient
(例如RealtimeClient.Service()
)。
錯誤處理
所有 非同步方法在遇到錯誤時都會拋出異常。為了進一步改善錯誤處理,會拋出不同類型的異常(請參考相關方法的摘要)。
雖然在代碼中添加 try/catch 塊有些繁瑣,但它使 API 更加簡單。
C#
try {
await client.ConnectUsingSettingsAsync(appSettings);
} catch (Exception e) {
Debug.LogException(e);
}
C#
try {
// Disconnecting can also fail
await client.DisconnectAsync();
} catch (Exception e) {
Debug.LogException(e);
}
Unity與非同步
在 Unity 中使用async
/await
時,有幾個特殊點需要注意:
與 .Net 不同,從 Unity 線程使用await
總是會在 Unity 線程上恢復執行。
- 這使得在 Unity 中使用 await 對我們的用途相當無害。但在 Unity 之外使用時會導致多線程問題。
新的Tasks
會運行在線程池上(在 Unity 中大多數情況下這是非常不理想的),除非使用基於 Unity SynchronizationContext 的自定義 TaskFactory 創建。
- 全局默認的
AsyncConfig
創建了一個TaskFactory
,內部用於創建和繼續任務。 - 可以參考
AsyncConfig.InitForUnity()
的實現。
C#
var taskFactory = new TaskFactory(
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.FromCurrentSynchronizationContext());
Unity 在切換遊戲模式時不會停止運行的Tasks
。
- 這是一個頭痛的問題。我們通過在
AsyncSetup
類中使用全局的CancellationTokenSource
來解決這個問題,該類會在遊戲模式改變時觸發回調(參考AsyncSetup.Startup()
)。 - 所有任務和延續任務在內部創建時,要麼使用顯式傳遞的
AsyncConfig
,要麼使用AsyncConfig.Global
。
在某些情況下,Unity 會抑制Tasks
中的異常。
- 簡而言之,使用以下模式:
public async void Update() {}
C#
// Does NOT log exception.
// Why? Because Unity does not handle exception inside tasks by design.
public Task Update1() {
return Task.Run(() => throw new Exception("peng"));
}
// Does NOT log exception.
// Why? Because we return at await and continue as a task object and Unity swallows the exception.
public async Task Update3() {
await Task.Delay(100);
throw new Exception("peng");
}
// Logs exception.
// Why? because we unwrap the task and run it synchronously with .Wait().
public void Update2() {
Task.Run(() => throw new Exception("peng")).Wait();
}
// Logs exception.
// Why? Because we resume the execution in this method and not return a task.
public async void Update4() {
await Task.Delay(100);
throw new Exception("peng");
}
// Logs exception.
// Why? We add a continuation task that logs (in any thread) when the task faulted.
public Task Update5() {
var task = Task.Run(() => throw new Exception("peng")).ContinueWith(t => {
if (t.IsFaulted) {
Debug.LogException(t.Exception.Flatten().InnerException);
};
});
return task;
}
WebGL需求
Realtime的非同步擴展功能支持 WebGL。由於瀏覽器中的線程限制,無法使用多線程代碼:
例如Task.Delay()
配對非同步擴展功能
Realtime配對擴展功能將最常見的連接和重新連接邏輯結合到兩個方便的擴展方法中,用於RealtimeClient
類。
ConnectToRoomAsync
C#
public Task<RealtimeClient> ConnectToRoomAsync(MatchmakingArguments arguments)
ConnectToRoomAsync
會執行以下操作:
- 使用提供的
PhotonSettings
及AuthValues
連接到 Photon Cloud - 根據配置執行簡單的配對
- 隨機配對:
RoomName:null
,CanOnlyJoin:false
- 加入現有房間:
RoomName:"room-name"
,CanOnlyJoin:false
- 加入或創建房間:
RoomName:"room-name"
,CanOnlyJoin:true
- 使用特定大廳:
Lobby:MyLobby
- 使用大廳屬性:
CustomLobbyProperties:MyLobbyProperties
- 隨機配對:
必須設置以下值:PhotonSettings
, MaxPlayer
, PluginName
, AuthValues
/ UserId
MatchmakingArguments
屬性 | 類型 | 描述 |
---|---|---|
PhotonSettings |
AppSettings |
包含 AppId 和 Photon 伺服器地址資訊的 Photon AppSetting 類。 |
PlayerTtlInSeconds |
int |
玩家 TTL,單位為秒。 |
EmptyRoomTtlInSeconds |
int |
空房間 TTL,單位為秒。 |
RoomName |
string |
設置要創建或加入的房間名稱。如果 RoomName 為 null,則使用隨機配對。 |
MaxPlayers |
int |
Photon 房間的最大客戶端數量。0 = 無限制。 |
CanOnlyJoin |
bool |
配置連接請求是否只能加入房間,還是也可以創建房間。 |
CustomProperties |
Hashtable |
自定義房間屬性,配置為 EnterRoomArgs.RoomOptions.CustomRoomProperties 。 |
CustomLobbyProperties |
string[] |
用於大廳配對的房間屬性列表。將配置為 EnterRoomArgs.RoomOptions.CustomRoomPropertiesForLobby 。 |
AsyncConfig |
AsyncConfig |
包含 TaskFactory 和全局取消支持的異步配置。如果為 null,則使用 AsyncConfig.Global 。 |
NetworkClient |
RealtimeClient |
可選提供客戶端對象。如果為 null,則在配對過程中創建新的客戶端對象。 |
AuthValues |
AuthenticationValues |
為 Photon 伺服器連接提供認證值。與自定義認證一起使用。設置 UserId 時會創建此字段。 |
PluginName |
string |
要連接到的 Photon 伺服器插件。 |
ReconnectInformation |
MatchmakingReconnectInformation |
可選對象,用於保存和加載重新連接資訊。 |
Lobby |
TypedLobby |
用於配對的可選 Realtime 大廳。 |
ReconnectToRoomAsync
C#
public Task<RealtimeClient> ReconnectToRoomAsync(MatchmakingArguments arguments)
ReconnectToRoomAsync
會嘗試返回之前的房間。
當客戶端對象處於可重用狀態時(例如超時後),它會嘗試快速重新連接到房間(跳過主伺服器)。否則,會運行完整的連接序列並嘗試重新加入房間。
如果伺服器尚未丟棄用戶之前的連接(例如在 10 秒超時期間),重新加入會失敗。ReconnectToRoomAsync
會自動處理這種特定情況,並多次嘗試重新加入房間,最終才會失敗。
ReconnectToRoomAsync
也可以在重啟應用程式或使用新客戶端對象後使用。在這些情況下,必須設置arguments.ReconnectInformation
(類型為MatchmakingReconnectInformation
)以提供重新加入房間的資訊。
在 Quantum 中,可以使用QuantumReconnectInformation
,它會自動將重新加入資訊保存到PlayerPrefs
中。
注意: 將UserId
保存到PlayerPrefs
可能存在安全風險,在應用程式重啟後進入重新連接邏輯之前,應始終使用自定義認證替換它。
Quantum演示菜單可以配置為在啟動時檢查保存的QuantumReconnectInformation
。使用QuantumMenuUIMain.IsReconnectionCheckEnabled
啟用此功能。
任何MatchmakingReconnectInformation
實例都有生命周期,以防止對過期會話的重新加入嘗試。在在線遊戲期間,反覆調用Set(client)
以刷新此超時。在成功連接或重新連接後,配對擴展方法會自動調用它。
如果需要,可以覆蓋虛擬方法MatchmakingReconnectInformation.Set(client)
。
C#
virtual void Set(RealtimeClient client)
MatchmakingReconnectInformation
屬性 | 類型 | 描述 |
---|---|---|
Room |
string |
客戶端之前連接的房間名稱。 |
Region |
string |
客戶端之前連接的區域。 |
AppVersion |
string |
之前連接中使用的應用版本。 |
UserId |
string |
客戶端用於連接伺服器的用戶 ID。 |
TimeoutInTicks |
long |
此資訊被視為不可用的超時時間。使用 Timeout 屬性來設置和獲取此值。 |