如何在集线器中使用异步/等待集线器。在SignalR客户端中打开

问题描述 投票:8回答:3

我有一个与SignalR集线器(服务器)通信的.Net Windows服务(客户端)。大多数客户端方法将花费一些时间来完成。从服务器接收呼叫时,我(或需要包装)目标方法/集线器。要避免出现警告,请执行以下操作:

“由于未等待此调用,因此在调用完成之前将继续执行当前方法。请考虑将await运算符应用于调用结果”

在客户端上,这是启动/设置代码的示例:

IHubProxy _hub
string hubUrl = @"http://localhost/";

var connection = new HubConnection(hubUrl, hubParams);
_hub = connection.CreateHubProxy("MyHub");
await connection.Start();

_hub.On<Message>("SendMessageToClient", i => OnMessageFromServer(i.Id, i.Message));
_hub.On<Command>("SendCommandToClient", i => OnCommandFromServer(i.Id, i.Command));

也是在客户端上,这是方法的示例:

public static async Task<bool> OnMessageFromServer(string Id, string message)
{
    try
    {
        var result = await processMessage(message);  //long running task
    }
    catch (Exception ex)
    {
        throw new Exception("There was an error processing the message: ", ex);
    }
    return result;
}

public static async Task<bool> OnCommandFromServer(string Id, string command)
{
    try
    {
        var result = await processCommand(command);  //long running task
    }
    catch (Exception ex)
    {
        throw new Exception("There was an error processing the message: ", ex);
    }
    return result;
}

最终,我认为_hub.On正在注册回调,而不是从服务器实际执行(调用)。我认为我需要进入实际执行的中间,等待On [X] FromServer的结果并返回结果。

*************更新的示例,带有更正的代码*********************]

IHubProxy _hub
string hubUrl = @"http://localhost/";

var connection = new HubConnection(hubUrl, hubParams);
_hub = connection.CreateHubProxy("MyHub");
await connection.Start();

//original
//_hub.On<Message>("SendMessageToClient", i => OnMessageFromServer(i.Id, i.Message));
//_hub.On<Command>("SendCommandToClient", i => OnCommandFromServer(i.Id, i.Command));

//new async 
_hub.On<Message>("SendMessageToClient", 
    async (i) => await OnMessageFromServer(i.Id, i.Message));

_hub.On<Message>("SendCommandToClient", 
    async (i) => await OnCommandFromServer(i.Id, i.Message));

//expanding to multiple parameters
_hub.On<Message, List<Message>, bool, int>("SendComplexParamsToClient", 
    async (p1, p2, p3, p4) => 
       await OnComplexParamsFromServer(p1.Id, p1.Message, p2, p3, p4));    

然后目标方法签名将类似于

public static async Task<bool> OnComplexParamsFromServer(string id, string message,
                 List<Message> incommingMessages, bool eatMessages, int repeat)
{
    try
    {
        var result = await processCommand(message);  //long running task
        if (result) 
        {
             // eat up your incoming parameters
        }
    }
    catch (Exception ex)
    {
        throw new Exception("There was an error processing the message: ", ex);
    }
    return result;
}

感谢@AgentFire的快速响应!

c# .net async-await signalr.client
3个回答
7
投票

这是一个等待无效的模式,请像这样使用它:

_hub.On<Message>("SendMessageToClient", async i => await OnMessageFromServer(i.Id, i.Message))

3
投票

我知道这很旧,但是接受的答案创建的Lambda是async void

但是如果存在未处理的异常,async void方法可能会使您的应用程序崩溃。读取herehere

这些文章确实说,由于事件,允许async void only,而这些是我们正在谈论的事件。但是,仍然可以例外的是,异常可能会使您的整个应用崩溃。因此,如果要执行此操作,请确保在可能会引发异常的任何地方具有try / catch块。

但是async void方法也可能导致意外行为,因为调用它的代码在开始执行其他操作之前没有等待它完成。

[记住,await的好处是ASP.NET可以关闭并执行其他操作,稍后再返回其余代码。通常那很好。但是在这种特定情况下,这可能意味着可以同时处理两条(或更多条)传入的消息,这是对首先完成的消息的折腾(完成处理的第一个消息可能不是传入的第一个) )。尽管这可能与您的情况无关紧要。

也许您最好等一下:

_hub.On<Message>("SendMessageToClient",
                 i => OnMessageFromServer(i.Id, i.Message).GetAwaiter().GetResult());

请参阅herehere以了解使用.GetAwaiter().GetResult()而不是.Wait()的好处。


0
投票

SignalR客户端被设计为按顺序调用处理程序方法,而无需交织。换句话说,“ SingleThreaded”。通常,您可以依赖于称为“ SingleThreaded”的所有处理程序方法来设计signalR客户端代码。 (我在引号中使用“ SingleThreaded”,因为...它不是单线程的,但是我们似乎没有表达顺序调用的异步方法的语言,而没有以概念上单线程的方式进行交织)

然而,此处讨论的“异步无效”方法打破了这种设计假设,并导致了意外的副作用,即客户端处理程序方法现在被并发调用。这是引起副作用的代码示例:

/// Yes this looks like a correct async method handler but the compiler is
/// matching the connection.On<int>(string methodName, Action<int> method)
/// overload and we get the "async-void" behaviour discussed above
connection.On<int>(nameof(AsyncHandler), async (i) => await AsyncHandler(i)));

/// This method runs interleaved, "multi-threaded" since the SignalR client is just
/// "fire and forgetting" it.
async Task AsyncHandler(int value) {
    Console.WriteLine($"Async Starting {value}");
    await Task.Delay(1000).ConfigureAwait(false);
    Console.WriteLine($"Async Ending {value}");
}

/* Example output:
Async Starting 0
Async Starting 1
Async Starting 2
Async Starting 3
Async Starting 4
Async Starting 5
Async Starting 6
Async Starting 7
Async Starting 8
Async Ending 2
Async Ending 3
Async Ending 0
Async Ending 1
Async Ending 8
Async Ending 7
*/

如果您使用的是ASP.NET Core,我们可以附加异步方法处理程序,并让客户端一次依次调用它们,而无需交织,也不会阻塞任何线程。我们利用SignalR中为ASP.NET Core引入的following override

IDisposable On(this HubConnection hubConnection, string methodName, Type[] parameterTypes,
                Func<object[], Task> handler)

这是实现它的代码。可悲的是,您编写的用于附加处理程序的代码有点晦涩,但在这里是:

/// Properly attaching an async method handler
connection.On(nameof(AsyncHandler), new[] { typeof(int) }, AsyncHandler);

/// Now the client waits for one handler to finish before calling the next.
/// We are back to the expected behaviour of having the client call the handlers
/// one at a time, waiting for each to finish before starting the next.
async Task AsyncHandler(object[] values) {
    var value = values[0];
    Console.WriteLine($"Async Starting {value}");
    await Task.Delay(1000).ConfigureAwait(false);
    Console.WriteLine($"Async Ending {value}");
}

/* Example output
Async Starting 0
Async Ending 0
Async Starting 1
Async Ending 1
Async Starting 2
Async Ending 2
Async Starting 3
Async Ending 3
Async Starting 4
Async Ending 4
Async Starting 5
Async Ending 5
Async Starting 6
Async Ending 6
Async Starting 7
Async Ending 7
*/

当然,现在您知道如何根据您的要求实现任何一种客户行为。如果您选择使用异步无效行为,则最好对此做个很好的注释,以免陷入其他程序员的陷阱,并确保您不会抛出未处理的任务异常。

© www.soinside.com 2019 - 2024. All rights reserved.