This document is about: QUANTUM 3
SWITCH TO

Async 확장

Async Extensions for the Realtime API는 잘 알려진 콜백 방식을 기반 위에 선택적으로 추가된 모델로, .Net Realtime SDK v5 이상에 포함되어 있습니다.

이들 비동기 메서드는 C#의 async/await와 Task(TAP)를 기반으로 하며, 유니티에서도 이미 지원됩니다(자세한 내용은 Microsoft의 TAP 문서를 참조하세요).
이 async API 구현은 성능보다는 사용 편의성에 중점을 두고 있습니다. 게임 플레이 루프(e.g. 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에 대한 Async 버전이 이미 구현되어 있습니다. 부족한 부분이 있으면 로컬에서 쉽게 추가하거나 Photon 팀에 요청할 수 있습니다. 관련 코드는 AsyncExtensions.cs 파일에서 확인할 수 있습니다.

어떠한 async 메서드도 await로 대기되기 전에는 처리나 전송을 시작하지 않습니다.

RealtimeClient는 async 작업이 완료될 때까지 업데이트(RealtimeClient.Service())할 필요가 없습니다.

오류 처리

모든 Async 메서드는 오류가 발생하면 예외를 던집니다. 보다 세밀한 오류 처리를 위해 다양한 예외 타입이 제공됩니다(관련 메서드 요약 참조).

코드에 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);
}

유니티와 Async

유니티에서 async/await를 사용할 때 고려해야 할 몇 가지 특수 사항이 있습니다:

“.NET과 달리, 유니티 스레드에서 await를 사용하면 항상 유니티 스레드에서 실행이 재개됩니다.”

  • 이는 유니티 환경에서는 await 사용이 크게 문제가 되지 않음을 의미합니다. 다만 유니티 외부에서 사용할 경우 다중 스레딩 이슈를 일으킬 수 있습니다.

“새로운 Task는 기본적으로 스레드 풀에서 실행되며(대부분의 경우 유니티에서는 바람직하지 않습니다), 커스텀 TaskFactory를 유니티의 SynchronizationContext로 생성하지 않으면 그렇게 동작합니다.”

  • 글로벌 기본 AsyncConfig는 내부적으로 TaskFactory를 생성 및 사용하여 Task를 만들고 이어서 실행합니다.
  • AsyncConfig.InitForUnity() 호출 흐름을 따라가 보면 이 동작을 확인할 수 있습니다.

C#

var taskFactory = new TaskFactory(
  CancellationToken.None,
  TaskCreationOptions.DenyChildAttach,
  TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously,
  TaskScheduler.FromCurrentSynchronizationContext());

유니티는 플레이 모드 전환 시 실행 중인 Task를 중지하지 않습니다.

  • 이는 골칫거리입니다. 이를 해결하기 위해 AsyncSetup 클래스 내부에 글로벌 CancellationTokenSource를 사용하며, 플레이 모드 변경 콜백(AsyncSetup.Startup())에서 이 토큰이 취소되도록 합니다.
  • 내부에서 생성된 모든 Task 및 연속 작업(continuations)은 명시적으로 전달된 AsyncConfigAsyncConfig.Global을 사용합니다.

유니티는 특정 상황에서 Task 내 예외를 무시할 수 있습니다.

  • 간단히 말해, 다음 패턴을 사용하세요:

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 Async 확장은 WebGL에서도 지원됩니다.
다만 브라우저의 스레딩 제한으로 인해 멀티스레딩 코드는 사용할 수 없습니다.

예: Task.Delay()는 WebGL에서 동작하지 않습니다.

매치메이킹 Async 확장

Realtime 매치메이킹 확장은 RealtimeClient 클래스를 위해 가장 일반적인 연결 및 재연결 로직을 두 가지 간편한 확장 메서드로 결합한 것입니다.

ConnectToRoomAsync

C#

public Task<RealtimeClient> ConnectToRoomAsync(MatchmakingArguments arguments)

ConnectToRoomAsync 메서드는 다음 작업을 수행합니다:

  • 제공된 PhotonSettingsAuthValues를 사용하여 Photon Cloud에 연결
  • 설정에 따라 간단한 매치메이킹 수행
    • 무작위 매치메이킹: RoomName:null, CanOnlyJoin:false
    • 기존 룸 참가: RoomName:"room-name", CanOnlyJoin:false
    • 룸 참가 또는 생성: RoomName:"room-name", CanOnlyJoin:true
    • 지정된 로비 사용: Lobby:MyLobby
    • 로비 속성 사용: CustomLobbyProperties:MyLobbyProperties

다음 값이 반드시 설정되어 있어야 합니다:
PhotonSettings, MaxPlayers, PluginName, AuthValues 또는 UserId

MatchmakingArguments

속성(Property) 타입(Type) 설명(Description)
PhotonSettings AppSettings AppId 및 Photon 서버 주소 정보를 포함한 Photon 설정 클래스입니다.
PlayerTtlInSeconds int 플레이어 TTL(Time To Live) 값(초 단위)입니다.
EmptyRoomTtlInSeconds int 빈 룸 유지 TTL 값(초 단위)입니다.
RoomName string 생성 또는 참가할 룸 이름을 설정합니다. `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는 애플리케이션을 재시작하거나 새 클라이언트 객체를 사용할 때도 사용할 수 있습니다.
이 경우,  재참가 정보를 제공하기 위해 MatchmakingReconnectInformation 타입의 arguments.ReconnectInformation을 설정해야 합니다.

Quantum에서는 QuantumReconnectInformation을 사용할 수 있으며, 이 클래스는 재참가 정보를 자동으로 PlayerPrefs에 저장합니다.

주의: UserIdPlayerPrefs에 저장하는 것은 보안상의 위험이 있으므로, 앱 재시작 후 재접속 로직을 사용하기 전에는 반드시 커스텀 인증으로 대체해야 합니다.

Quantum 데모 메뉴는 시작 시 저장된 QuantumReconnectInformation을 확인하도록 설정할 수 있습니다.
QuantumMenuUIMain.IsReconnectionCheckEnabled를 사용하여 해당 기능을 활성화하세요.

모든 MatchmakingReconnectInformation 인스턴스는 시간제한이 있어 오래된 세션에 대해 재참가 시도를 차단합니다.
온라인 게임 중에는 이 타임아웃을 갱신하기 위해 Set(client)를 반복 호출하세요.
매치메이킹 확장 메서드는 연결 또는 재연결이 성공할 때 자동으로 이를 호출합니다.

필요에 따라 가상 메서드 MatchmakingReconnectInformation.Set(client)를 재정의할 수 있습니다.

C#

virtual void Set(RealtimeClient client)

MatchmakingReconnectInformation

속성(Property) 타입(Type) 설명
Room string 클라이언트가 연결된 룸 이름.
Region string 클라이언트가 연결된 리전.
AppVersion string 이전 연결에서 사용된 앱 버전.
UserId string 클라이언트가 서버에 연결할 때 사용한 사용자 ID.
TimeoutInTicks long 이 정보가 더 이상 유효하지 않다고 간주될 때까지의 타임아웃(틱 단위). 이 값은 Timeout 속성을 통해 설정하고 조회할 수 있습니다.
Back to top