如何将 async/await 与集线器一起使用.在 SignalR 客户端中打开
本文关键字:SignalR 客户端 一起 async await 集线器 | 更新日期: 2023-09-27 18:34:13
我有一个与SignalR Hub(服务器(通信的.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的快速响应!!
这是一个可等待空的模式,像这样使用它:
_hub.On<Message>("SendMessageToClient", async i => await OnMessageFromServer(i.Id, i.Message))
这很旧,但是接受的答案创建了一个async void
的lambda。
但是,如果存在未经处理的异常,async void
方法都可能导致应用崩溃。阅读 这里 和 这里.
这些文章确实说async void
只是因为事件才被允许的,而这些就是我们正在谈论的事件。但是,异常仍然可能导致整个应用程序崩溃。因此,如果您要使用它,请确保在可能抛出异常的任何地方都有 try
/catch
块。
但是async void
方法也可能导致意外行为,因为调用它的代码在关闭并执行其他操作之前不会等待它完成。
请记住,await
的好处是 ASP.NET 可以执行其他操作,稍后再返回到代码的其余部分。通常这很好。但在这种特定情况下,这可能意味着可以同时处理两个(或更多(传入消息,并且对于哪个消息首先完成是一个折腾(第一个完成处理的消息可能不是第一个进来的(。尽管这在您的情况下可能很重要,也可能无关紧要。
你最好只是等待它:
_hub.On<Message>("SendMessageToClient",
i => OnMessageFromServer(i.Id, i.Message).GetAwaiter().GetResult());
请参阅此处和此处,以获取使用.GetAwaiter().GetResult()
而不是.Wait()
的好处。
SignalR 客户端旨在按顺序调用处理程序方法,而无需交错。换句话说,"单线程"。通常,可以依赖于称为"单线程"的所有处理程序方法来设计 signalR 客户端代码。(我在引号中使用"单线程",因为...它不是单线程的,但我们似乎没有语言来表达按顺序调用的异步方法,而无需以概念上的单线程方式交错(
但是,此处讨论的"async-void"方法打破了此设计假设,并导致现在并发调用客户端处理程序方法的意外副作用。下面是导致副作用的代码示例:
/// 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 核心。
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
*/
当然,现在您知道如何根据您的要求实现任何一种客户行为。如果你选择使用 async-void 行为,最好对此进行很好的注释,这样你就不会困住其他程序员,并确保你不会抛出未处理的任务异常。