babalae/better-genshin-impact · warning · TimeoutException

图片下载超时({DownloadTimeout.TotalSeconds:0} 秒)。

Error message

图片下载超时({DownloadTimeout.TotalSeconds:0} 秒)。

What it means

Thrown when the banner image HTTP download does not complete within the hard-coded DownloadTimeout (30 seconds). The service links a CancellationTokenSource(DownloadTimeout) to the caller's token; if the timeout token fires but the caller's does not, the resulting OperationCanceledException is re-wrapped as TimeoutException so callers can distinguish a user-cancel from a network stall.

Source

Thrown at BetterGenshinImpact/Service/BannerImageService.cs:153

            lock (_fileCommitLock)
            {
                if (operationId != Volatile.Read(ref _latestOperationId))
                {
                    return false;
                }

                linkedCancellationToken.ThrowIfCancellationRequested();
                // 保存图片到本地
                File.Move(tempPath, NetworkImagePath, true);
            }

            return true;
        }
        catch (OperationCanceledException ex) when (
            timeoutCancellationTokenSource.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
        {
            throw new TimeoutException($"图片下载超时({DownloadTimeout.TotalSeconds:0} 秒)。", ex);
        }
        finally
        {
            TryDeleteFile(tempPath);
        }
    }

    public void InvalidatePendingDownloads()
    {
        Interlocked.Increment(ref _latestOperationId);
    }

    public void ResetNetworkImage()
    {
        InvalidatePendingDownloads();
        lock (_fileCommitLock)
        {
            if (File.Exists(UrlConfigPath))

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Use a smaller or better-hosted banner image URL so the full download finishes well under 30s.
  2. Verify network connectivity and proxy settings — a stalled TCP connection is the most common cause.
  3. If a longer deadline is acceptable, raise the DownloadTimeout constant in BannerImageService.cs (currently 30s) to fit the expected image size and bandwidth.
  4. Pre-compress the image and host it on a fast CDN.

Example fix

// before
private static readonly TimeSpan DownloadTimeout = TimeSpan.FromSeconds(30);

// after (if larger images are expected)
private static readonly TimeSpan DownloadTimeout = TimeSpan.FromSeconds(60);
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate reachability and expected size before committing to the timed download.
using var probe = await _httpClient.HeadAsync(uri, cancellationToken);
probe.EnsureSuccessStatusCode();
if (probe.Content.Headers.ContentLength is long len && len > MaxDownloadBytes)
{
    throw new InvalidDataException($"图片大小超过 {MaxDownloadBytes / 1024 / 1024} MB 限制。");
}

Try / catch

try
{
    await bannerImageService.DownloadAndSaveAsync(url, cancellationToken);
}
catch (TimeoutException ex)
{
    // Surface a user-facing message; the inner OperationCanceledException is preserved.
    logger.LogWarning(ex, "Banner download timed out for {Url}", url);
}

Prevention

When it happens

Trigger: Calling BannerImageService.DownloadAndSaveAsync against a slow or stalled server. HttpClient.GetAsync with HttpCompletionOption.ResponseHeadersRead blocks past 30s on header arrival, or the streaming ReadAsync loop stalls mid-body (connection alive, no bytes flowing). The linked token cancels and the `when` filter converts it.

Common situations: User sets a large remote banner URL behind a slow CDN or proxy; transient network congestion; server rate-limiting; image host geo-blocked causing TCP stalls.

Understand the failure class

Related errors


AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13). Data as JSON: /api/errors/59f23ae24874d2fb. Report an issue: GitHub.