FiveHeartsの技術記事

実用的なUnityWebRequestのtimeoutの実装方法

ゲーム開発において、UnityWebRequestにて通信を行う場面は
多々あると思います。
一般的には以下のように、UnityWebRequestに定義されている
timeoutを使えば良いのですが、
実際に商業ベースにのせるとなるとこれでは不完全です。

				  
//3秒でタイムアウト
unityWebRequest.timeout = 3;
				  
				

まずなぜ不完全なのかを解説します。
それはこのtimeoutがiOS環境においては
必ずしも発火しないためです。
timeoutの判定が取られるということは
サーバーとの通信に失敗した時ですが、
その時の約5%くらいでtimeoutが発火しないのです。
つまり全体で言うと発生確率はかなり低いのですが、
この現象が発生した場合、通信におけるエラーハンドリングを抜けてきます。
当然これはバグの原因になります。
そのため以下のように実装してこれを回避します

				
public struct Result
{
	public bool isTimeoutRetry;   //タイムアウトでリトライするときのフラグ

	public void Timeout()
	{
		isTimeoutRetry = true;
	}
}

public void Send<T>(ref T request, Action<Result> cb)
{
	// リクエストオブジェクトを JSON に変換(byte配列)
	reqJson = JsonUtility.ToJson(request);
	// (中略)
	byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(reqJson);
	// HTTP(POST)通信
	CoroutineHandler.StartStaticCoroutine(onSend(url, dataBytes, cb));
}

private IEnumerator onSend(string url, byte[] postData, Action<Result> cb)
{
	//リクエスト送信
	var req = new UnityWebRequest(url, "POST");

	float addTime = 0f;//タイムアウト監視用
	AsyncOperation op = req.SendWebRequest();
	while (true)
	{
		if (op.isDone == false)
		{
			//通信中
			yield return null;
			addTime += Time.deltaTime;
			if (addTime >= 3)
			{
				//3秒経過したらループを抜ける

				//通信を切断する
				req.Abort();//通信切断
				req.Dispose();//通信のメモリのリリース
				Result result = new Result();
				Debug.Log("サーバとの通信に失敗");
				result.Timeout();
				cb(result);
				yield break;
			}

		}
		else
		{
			//通信完了でループを抜ける
			break;
		}
	}
}
				
				

重要なのはonSend関数のタイムアウト処理です。
その他の関数や定義については
公開できる範囲に限定して省略して書いています。
実際にこのまま商用ベースにのせれるものではありませんので
参考にはなると思いますがご注意ください。

通信を行う際に呼ばれるのはSend関数です。
CoroutineHandler.StartStaticCoroutine
この部分はMonoBehaviourを継承せずにコルーチンを
使うための独自クラスのstaticな関数です。
onSend関数ではタイムアウトの部分だけを記載しています。
実際に使うにはエラーハンドリングやデータの整合性検証などの
追加実装を行ってください。
今回お伝えしたい点はtimeoutを
そのまま使うのは良くないということです

今回の記事はここまでです。
それではまた別の記事でお会いしましょう。