You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SignalR WebSockets中并行发起请求?解决图片瓦片加载缓慢问题

Ah, I've run into this exact issue with SignalR and tile-based loading before—let me break down why you're seeing serial processing and how to fix it.

By default, SignalR limits a single client connection to processing one invocation at a time (the default value of MaxConcurrentInvocationsPerClient is set to 1). So when you fire off 100 tile requests over one connection, they queue up and process sequentially, which explains that slow 50-second wait (100 * 0.5s).

Here are the most effective solutions to enable parallel processing:

Fix 1: Increase Concurrent Invocations Per Client

The simplest fix is to adjust SignalR's server-side configuration to allow multiple concurrent invocations on a single connection. This lets your client send multiple tile requests that the server processes in parallel.

In your server's Startup/Program.cs, configure the Hub options:

builder.Services.AddSignalR(options =>
{
    // Allow up to 10 concurrent invocations per client (tune this based on your server capacity)
    options.MaxConcurrentInvocationsPerClient = 10;
});

On the client side, you can now fire off multiple requests without waiting for each to complete. For example (JavaScript client):

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/tileHub")
    .build();

// Fire off 10 parallel requests
const tileRequests = [];
for (let i = 0; i < 10; i++) {
    const x = /* tile x coordinate */;
    const y = /* tile y coordinate */;
    tileRequests.push(connection.invoke("GetTile", x, y));
}

// Wait for all to complete and render tiles
Promise.all(tileRequests)
    .then(tiles => {
        tiles.forEach(tile => renderTile(tile));
    })
    .catch(err => console.error(err));
Fix 2: Use Multiple Client Connections

If you need even more parallelism (or can't adjust the server config), create multiple independent SignalR connections from the client. Each connection will handle its own queue of requests, so multiple connections mean multiple parallel processing pipelines.

Just be careful not to create too many connections (aim for 5-16, depending on server resources) to avoid overwhelming the server:

// Create 5 separate connections
const connections = Array.from({ length: 5 }, () => 
    new signalR.HubConnectionBuilder()
        .withUrl("/tileHub")
        .build()
);

// Initialize all connections
await Promise.all(connections.map(conn => conn.start()));

// Distribute tile requests across connections
const tileCoordinates = [/* your list of (x,y) tile positions */];
tileCoordinates.forEach((coord, index) => {
    const connection = connections[index % connections.length];
    connection.invoke("GetTile", coord.x, coord.y)
        .then(tile => renderTile(tile, coord.x, coord.y))
        .catch(err => console.error(`Failed to load tile ${coord.x},${coord.y}:`, err));
});
Fix 3: Batch Tile Requests (Most Efficient)

Instead of sending individual requests per tile, send batches of tile coordinates in one invocation. On the server, process all tiles in the batch in parallel using Task.WhenAll, then return all tiles at once. This reduces the number of SignalR messages and is easier on both client and server.

Server-side Hub method:

public async Task<List<byte[]>> GetTilesBatch(List<(int X, int Y)> tileCoords)
{
    // Generate all tiles in parallel
    var tileTasks = tileCoords.Select(coord => GenerateTileAsync(coord.X, coord.Y));
    return await Task.WhenAll(tileTasks);
}

// Ensure your tile generation is truly async (no blocking calls!)
private async Task<byte[]> GenerateTileAsync(int x, int y)
{
    // Simulate 0.5s generation time (replace with your actual JPG generation logic)
    await Task.Delay(500);
    // Return the JPG byte array for the tile
    return GenerateTileImage(x, y);
}

Client-side code:

// Group tiles into batches of 20 (adjust based on your server's capacity)
const batchSize = 20;
const tileBatches = [];
const tileCoordinates = [/* your list of (x,y) positions */];

for (let i = 0; i < tileCoordinates.length; i += batchSize) {
    tileBatches.push(tileCoordinates.slice(i, i + batchSize));
}

// Process each batch in parallel
Promise.all(tileBatches.map(batch => 
    connection.invoke("GetTilesBatch", batch)
        .then(tiles => {
            tiles.forEach((tile, index) => {
                const coord = batch[index];
                renderTile(tile, coord.x, coord.y);
            });
        })
))
.catch(err => console.error("Batch request failed:", err));
Key Best Practices
  • Cache Tiles: Once a tile is generated, cache it on the server (e.g., in memory or Redis) so repeat requests don't reprocess the same tile. This will drastically reduce load over time.
  • Throttle Requests: Even with parallelism, don't flood the server with hundreds of requests at once. Use a request queue with concurrency limits (e.g., 20 concurrent requests max) to avoid overwhelming your server's CPU/memory.
  • Avoid Blocking Code: Make sure your tile generation logic is truly asynchronous (no blocking calls like Thread.Sleep). Blocking code will tie up server threads and limit parallelism.

内容的提问来源于stack exchange,提问作者Peter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:19:05