[英]SignalR multi client method call async return - .Net Core 6
我正在尝试使用 signalR 来触发流程并实时从客户那里获得回报。 我的使用场景由 2 种客户端类型(流程管理器和存储)和一种服务器(集线器)组成。
第一个客户端类型是“进程管理器”。 此客户端将用于管理流程执行。 它将调用集线器的(服务器)方法,要求它在某个连接上调用某个方法。 然后集线器将在该连接上调用该方法并“等待”答案。 第二种客户端类型(“Store”)将执行调用的方法并通过调用响应方法“返回”到集线器。 然后,该集线器方法将触发等待返回的任务的继续,然后响应最终将到达流程管理器。
PM -- (invokes)--> HUB -- (invokes)--> STORE -- (invokes)--> HUB -- (return)--> PM
我的问题是我需要异步调用多个方法,此时,客户端方法的调用正在锁定后续调用。
通过一些研究,我设法编写了下面的代码。 我调用了两个方法,GetLocalIpAddress 和 GetMachineName。 GetLocalIpAddress 故意添加了 5 秒的延迟来模拟 GetMachineName 是否会首先返回。
发生的事情是 GetMachineName 在返回之前等待 GetLocalIpAddress 完成。
是否可以异步进行这些调用? 什么先执行先返回,不管其他方法是什么? 对于不同的方法和多次调用的同一方法,我需要这种行为。 例如,假设一个执行查询的方法。 执行不同的查询可能需要比其他查询更长的时间。 我想在方法执行后立即返回数据,而不管首先发送的同一方法的其他调用仍在运行。
我对异步代码没有太多经验。 所以请不要犹豫,指出或改变任何错误。
PM 客户端 - 控制台应用程序
var uri = "https://localhost:7155/SignalR";
HubConnection connection = new HubConnectionBuilder()
.WithUrl(uri, (opts) =>
{
opts.Headers.Add("ID", "ProcessManager");
}).Build();
await connection.StartAsync();
HubRequest(0, "Store0", "GetLocalIpAddress");
HubRequest(1, "Store0", "GetMachineName");
Console.ReadLine();
async void HubRequest(int i, string connectionID, string methodName, string? data = null)
{
Console.WriteLine($"{i} - Request {methodName} from remote client - {connectionID}");
string response = await connection.InvokeAsync<string>("ExecuteOnClient", connectionID, methodName, data);
Console.WriteLine($"{i} - Response {methodName} from remote client: {response} - {connectionID}");
}
服务器 - Asp.net 核心 Web 应用程序
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
var app = builder.Build();
app.MapHub<MainHub>("/SignalR");
app.MapGet("/", () => "Hub initialized");
app.Run();
public class MainHub : Hub
{
static Dictionary<string, string> connectionsList = new Dictionary<string, string>();
private static readonly ConcurrentDictionary<Guid, TaskCompletionSource<object>> _responses = new ();
public async override Task OnConnectedAsync()
{
HttpRequest request = Context.GetHttpContext().Request;
string id = request.Headers["ID"].ToString().Trim();
string connectionId = Context.ConnectionId;
if (!id.Equals(("ProcessManager")))
{
id = $"{id}{connectionsList.Where(x => x.Key.Contains("Store")).Count()}";
await Clients.Client(connectionId).SendAsync("AfterConnected", id);
}
connectionsList.Add(id, connectionId);
}
public async override Task OnDisconnectedAsync(Exception exception)
{
string connectionId = Context.ConnectionId;
foreach (var item in connectionsList.Where(kvp => kvp.Value == connectionId).ToList())
connectionsList.Remove(item.Key);
}
public void ClientResponse(Guid guidRequestID, string returnData)
{
TaskCompletionSource<object> tcs;
if (_responses.TryGetValue(guidRequestID, out tcs))
{
// Trigger the task continuation
tcs.TrySetResult(returnData);
}
//else
//{
// Client response for something that isn't being tracked, might be an error
//Test Only
//throw new Exception("Resposta não Esperada.");
//}
}
public async Task<string> ExecuteOnClient(string connectionID, string methodName, string data)
{
// Create an entry in the dictionary that will be used to track the client response
var tcs = new TaskCompletionSource<object>();
var guidRequestID = Guid.NewGuid();
_responses.TryAdd(guidRequestID, tcs);
// Call method on the client passing the identifier
if (!string.IsNullOrWhiteSpace(data))
Clients.Client(connectionsList[connectionID]).SendAsync(methodName, guidRequestID, data);
else
Clients.Client(connectionsList[connectionID]).SendAsync(methodName, guidRequestID);
try
{
// Wait for the client to respond
int timeout = 10000;
var task = tcs.Task;
if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
{
return (string)await task;
}
else
{
//Cancel this task if the client disconnects (potentially by just adding a timeout)
return "TimedOut";//Test
}
}
finally
{
// Remove the tcs from the dictionary so that we don't leak memory
_responses.TryRemove(guidRequestID, out tcs);
}
}
}
商店客户端 - 控制台应用程序
string uri = "https://localhost:7155/SignalR";
HubConnection connection = new HubConnectionBuilder()
.WithUrl(uri, (opts) =>
{
opts.Headers.Add("ID", "Store");
}).Build();
connection.On<string>("AfterConnected", (data) =>
{
Console.WriteLine($"Client name - {data}");
});
connection.On<string>("GetLocalIpAddress", (guidRequestID) =>
{
Console.WriteLine($"GetLocalIpAddress - {GetLocalIPAddress()}");
Thread.Sleep(5000);
connection.InvokeAsync("ClientResponse", guidRequestID, GetLocalIPAddress());
});
connection.On<string>("GetMachineName", (guidRequestID) =>
{
Console.WriteLine($"GetMachineName - {System.Environment.MachineName}");
connection.InvokeAsync("ClientResponse", guidRequestID, System.Environment.MachineName);
});
await connection.StartAsync();
Console.ReadKey();
static string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip.ToString();
}
}
throw new Exception("No network adapters with an IPv4 address in the system!");
}
有两种选择:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.