来自客户端方法的 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 值进入集线器?