✂️

UnityWebRequestで長めのtimeoutを設定するけど一定時間進捗がなかったら止めたい

2023/07/28に公開

やりたいこと

たまに4Gくらいのファイルをダウンロードしなければならないことがあります。
通信環境によっては途中で通信が止まってしまい、いくら待ってもダウンロードが続かないということが起こり得ます。そういうときは早めに諦めてユーザに時間を返してあげたいところです。
こういうとき、途中でダウンロードが止まっていたとしてもUnityWebRequestはギブアップはしてくれません。SendWebRequestが実行された後、設定されたtimeoutが経過するまでそのままです。

ファイルサイズが大きいのでUnityWebRequest.timeoutは長めに設定しておきたい。でも通信が途中で止まっているなら検知したい。

どうやるか

幸いにもUnityWebRequest.downloadedBytesで現在どれだけデータをダウンロードしているかは確認できます。この値を一定間隔毎にチェックして、変わっていなかったら通信が止まっているということにすればいいわけです。

コード

こんな感じです。あまり関係ない部分は雑に書いてます。

private async UniTask DownloadHeavy(CancellationToken token)
{
    using var compositeDisposable = new CompositeDisposable();

    // とても重いコンテンツ
    var url = "http://localhost:8888/heavy.txt";
    var unityWebRequest = UnityWebRequest.Get(url);
    compositeDisposable.Add(unityWebRequest);

    // タイムアウトは1時間
    const int timeout = 60 * 60;
    unityWebRequest.timeout = timeout;

    // 通信なしタイムアウトは10秒
    const int noProgressTimeout = 10;
    Observable.Interval(TimeSpan.FromSeconds(noProgressTimeout))
        .Select(_ => unityWebRequest.downloadedBytes)
        .Pairwise()
        .Where(pair => pair.Previous == pair.Current)
        .Subscribe(_ => { unityWebRequest.Abort(); })
        .AddTo(compositeDisposable);

    try
    {
        await unityWebRequest.SendWebRequest().WithCancellation(token);
    }
    catch (Exception e)
    {
        Debug.LogException(e);
    }
}

要点

要点に解説のコメントをつけるとこうです。

// 一定時間毎にイベントを発火
Observable.Interval(TimeSpan.FromSeconds(noProgressTimeout))
// 現在のダウンロードデータ量に変換.これで一定時間毎のダウンロードデータ量が流れる
.Select(_ => unityWebRequest.downloadedBytes)
// 前の値と今の値をまとめて送る
.Pairwise()
// 前の値と今の値を比較
.Where(pair => pair.Previous == pair.Current)
// 前回から進んでいないので中止
.Subscribe(_ => { unityWebRequest.Abort(); })

これで一定時間ダウンロードが進んでいなかったら中止できるようになりました。

まとめ

こんなん手作りしてるのおかしい……おかしくない……?
なんかC#ってRetrofitみたいなコレ! って通信ライブラリがないですよね。HttpClientは特定のフレームワークにフォーカスしすぎな気がして使いづらいです。かといって通信をネイティブで実装するのはマルチプラットフォーム対応だとあまりにも厳しい。もろもろのことを考えるとUnityWebRequestでいいか……ってなります。結局こうやってラッパーを書く羽目になるにせよ……。

おしまい。

Discussion