来自客户端方法的 SignalR return 值
SignalR return value from client method
您好,我正在开发一个与 SignalR 通信的服务器-客户端应用程序。我必须实现的是一种机制,允许我的服务器调用客户端上的方法并获得该调用的结果。这两个应用程序都是使用 .Net Core 开发的。
我的概念是,服务器调用客户端上提供该调用的 Id 的方法,客户端执行该方法并作为响应调用服务器上的方法,并提供方法结果和提供的 Id,以便服务器可以将调用与结果。
用法是这样的:
var invocationResult = await Clients
.Client(connectionId)
.GetName(id)
.AwaitInvocationResult<string>(ClientInvocationHelper._invocationResults, id);
AwaitInvocationResult - is a extension method to Task
public static Task<TResultType> AwaitInvocationResult<TResultType>(this Task invoke, ConcurrentDictionary<string, object> lookupDirectory, InvocationId id)
{
return Task.Run(() =>
{
while (!ClientInvocationHelper._invocationResults.ContainsKey(id.Value)
|| ClientInvocationHelper._invocationResults[id.Value] == null)
{
Thread.Sleep(500);
}
try
{
object data;
var stingifyData = lookupDirectory[id.Value].ToString();
//First we should check if invocation response contains exception
if (IsClientInvocationException(stingifyData, out ClientInvocationException exception))
{
throw exception;
}
if (typeof(TResultType) == typeof(string))
{
data = lookupDirectory[id.Value].ToString();
}
else
{
data = JsonConvert.DeserializeObject<TResultType>(stingifyData);
}
var result = (TResultType)data;
return Task.FromResult(result);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
});
}
如您所见,基本上我有一个字典,其中键是调用 ID,值是客户端可以报告的调用结果。在 while 循环中,我正在检查结果是否已可供服务器使用,如果是,则将结果转换为特定类型。
此机制运行良好,但我观察到我不理解的奇怪行为。
如果我使用 await
修饰符调用此方法,则永远不会调用 Hub 中负责从客户端接收结果的方法。
///This method gets called by the client to return a value of specific invocation
public Task OnInvocationResult(InvocationId invocationId, object data)
{
ClientInvocationHelper._invocationResults[invocationId.Value] = data;
return Task.CompletedTask;
}
结果 AwaitInvocationResult
的 while 循环永远不会结束,集线器被阻塞。
也许有人可以向我解释这种行为,这样我就可以改变我的方法或改进我的代码。
默认情况下,客户端在服务器上一次只能有一个集线器方法 运行ning。这意味着当您在第一个集线器方法中等待结果时,第二个集线器方法永远不会 运行 因为第一个集线器方法阻塞了处理循环。
如果 OnInvocationResult
方法 运行 您的 AwaitInvocationResult
扩展中的逻辑和第一个集线器方法只注册 ID 并调用客户端,那就更好了。
正如 Brennan 在回答中提到的那样,在 ASP.NET Core 5.0 SignalR 连接之前只能处理一个非流式调用 hub 方法。由于您的调用被阻止,服务器无法处理下一次调用。
但在这种情况下,您可能可以尝试在单独的集线器中处理客户端响应,如下所示。
public class InvocationResultHandlerHub : Hub
{
public Task HandleResult(int invocationId, string result)
{
InvoctionHelper.SetResult(invocationId, result);
return Task.CompletedTask;
}
}
虽然 hub 方法调用被阻止,但调用者连接不能调用其他 hub 方法。但是由于客户端对每个集线器都有单独的连接,他将能够调用其他集线器上的方法。可能不是最好的方法,因为在发布响应之前客户端将无法到达第一个集线器。
您可以尝试的其他方法是流式调用。目前 SignalR 不等待它们处理下一条消息,因此服务器将处理流调用之间的调用和其他消息。
您可以在 Invoke 方法中检查此行为,流式传输时不等待调用
https://github.com/dotnet/aspnetcore/blob/c8994712d8c3c982111e4f1a09061998a81d68aa/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs#L371
因此您可以尝试添加一些您不会使用的虚拟流参数:
public async Task TriggerRequestWithResult(string resultToSend, IAsyncEnumerable<int> stream)
{
var invocationId = InvoctionHelper.ResolveInvocationId();
await Clients.Caller.SendAsync("returnProvidedString", invocationId, resultToSend);
var result = await InvoctionHelper.ActiveWaitForInvocationResult<string>(invocationId);
Debug.WriteLine(result);
}
并且在客户端,您还需要创建并填充此参数:
var stringResult = document.getElementById("syncCallString").value;
var dummySubject = new signalR.Subject();
resultsConnection.invoke("TriggerRequestWithResult", stringResult, dummySubject);
dummySubject.complete();
更多详情:https://docs.microsoft.com/en-us/aspnet/core/signalr/streaming?view=aspnetcore-5.0
如果你可以使用 ASP.NET Core 5,你可以尝试使用新的 MaximumParallelInvocationsPerClient hub 选项。它将允许针对一个连接并行执行多个调用。但是,如果您的客户端在不提供结果的情况下调用过多的集线器方法,连接就会挂起。
更多详情:https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration?view=aspnetcore-5.0&tabs=dotnet
实际上,由于 return 来自客户端调用的值不是由 SignalR 实现的,也许您可以尝试查看流以 return 值进入集线器?
您好,我正在开发一个与 SignalR 通信的服务器-客户端应用程序。我必须实现的是一种机制,允许我的服务器调用客户端上的方法并获得该调用的结果。这两个应用程序都是使用 .Net Core 开发的。
我的概念是,服务器调用客户端上提供该调用的 Id 的方法,客户端执行该方法并作为响应调用服务器上的方法,并提供方法结果和提供的 Id,以便服务器可以将调用与结果。
用法是这样的:
var invocationResult = await Clients
.Client(connectionId)
.GetName(id)
.AwaitInvocationResult<string>(ClientInvocationHelper._invocationResults, id);
AwaitInvocationResult - is a extension method to Task
public static Task<TResultType> AwaitInvocationResult<TResultType>(this Task invoke, ConcurrentDictionary<string, object> lookupDirectory, InvocationId id)
{
return Task.Run(() =>
{
while (!ClientInvocationHelper._invocationResults.ContainsKey(id.Value)
|| ClientInvocationHelper._invocationResults[id.Value] == null)
{
Thread.Sleep(500);
}
try
{
object data;
var stingifyData = lookupDirectory[id.Value].ToString();
//First we should check if invocation response contains exception
if (IsClientInvocationException(stingifyData, out ClientInvocationException exception))
{
throw exception;
}
if (typeof(TResultType) == typeof(string))
{
data = lookupDirectory[id.Value].ToString();
}
else
{
data = JsonConvert.DeserializeObject<TResultType>(stingifyData);
}
var result = (TResultType)data;
return Task.FromResult(result);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
});
}
如您所见,基本上我有一个字典,其中键是调用 ID,值是客户端可以报告的调用结果。在 while 循环中,我正在检查结果是否已可供服务器使用,如果是,则将结果转换为特定类型。
此机制运行良好,但我观察到我不理解的奇怪行为。
如果我使用 await
修饰符调用此方法,则永远不会调用 Hub 中负责从客户端接收结果的方法。
///This method gets called by the client to return a value of specific invocation
public Task OnInvocationResult(InvocationId invocationId, object data)
{
ClientInvocationHelper._invocationResults[invocationId.Value] = data;
return Task.CompletedTask;
}
结果 AwaitInvocationResult
的 while 循环永远不会结束,集线器被阻塞。
也许有人可以向我解释这种行为,这样我就可以改变我的方法或改进我的代码。
默认情况下,客户端在服务器上一次只能有一个集线器方法 运行ning。这意味着当您在第一个集线器方法中等待结果时,第二个集线器方法永远不会 运行 因为第一个集线器方法阻塞了处理循环。
如果 OnInvocationResult
方法 运行 您的 AwaitInvocationResult
扩展中的逻辑和第一个集线器方法只注册 ID 并调用客户端,那就更好了。
正如 Brennan 在回答中提到的那样,在 ASP.NET Core 5.0 SignalR 连接之前只能处理一个非流式调用 hub 方法。由于您的调用被阻止,服务器无法处理下一次调用。
但在这种情况下,您可能可以尝试在单独的集线器中处理客户端响应,如下所示。
public class InvocationResultHandlerHub : Hub
{
public Task HandleResult(int invocationId, string result)
{
InvoctionHelper.SetResult(invocationId, result);
return Task.CompletedTask;
}
}
虽然 hub 方法调用被阻止,但调用者连接不能调用其他 hub 方法。但是由于客户端对每个集线器都有单独的连接,他将能够调用其他集线器上的方法。可能不是最好的方法,因为在发布响应之前客户端将无法到达第一个集线器。
您可以尝试的其他方法是流式调用。目前 SignalR 不等待它们处理下一条消息,因此服务器将处理流调用之间的调用和其他消息。 您可以在 Invoke 方法中检查此行为,流式传输时不等待调用 https://github.com/dotnet/aspnetcore/blob/c8994712d8c3c982111e4f1a09061998a81d68aa/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs#L371
因此您可以尝试添加一些您不会使用的虚拟流参数:
public async Task TriggerRequestWithResult(string resultToSend, IAsyncEnumerable<int> stream)
{
var invocationId = InvoctionHelper.ResolveInvocationId();
await Clients.Caller.SendAsync("returnProvidedString", invocationId, resultToSend);
var result = await InvoctionHelper.ActiveWaitForInvocationResult<string>(invocationId);
Debug.WriteLine(result);
}
并且在客户端,您还需要创建并填充此参数:
var stringResult = document.getElementById("syncCallString").value;
var dummySubject = new signalR.Subject();
resultsConnection.invoke("TriggerRequestWithResult", stringResult, dummySubject);
dummySubject.complete();
更多详情:https://docs.microsoft.com/en-us/aspnet/core/signalr/streaming?view=aspnetcore-5.0
如果你可以使用 ASP.NET Core 5,你可以尝试使用新的 MaximumParallelInvocationsPerClient hub 选项。它将允许针对一个连接并行执行多个调用。但是,如果您的客户端在不提供结果的情况下调用过多的集线器方法,连接就会挂起。 更多详情:https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration?view=aspnetcore-5.0&tabs=dotnet
实际上,由于 return 来自客户端调用的值不是由 SignalR 实现的,也许您可以尝试查看流以 return 值进入集线器?