Question

I'm using SignalR 2.0.1 with PersistentConnection (not hubs) and currently my default very simple OnReceived handler looks like this:

protected override Task OnReceived(IRequest request, string connectionId, string data)
{
    return Connection.Broadcast(data);
}

I want to add some I/O-bound code to this handler, like accessing a DB on a different machine. Naturally I want everything to be asynchronous and I don't want to block the thread so I'm hoping to use an async flavor like db.SaveChangesAsync() of EF6 (instead of the regular blocking db.SaveChanges).

I added the db.SaveChangesAsync() to the handler, but I need to await on it too. So I've also added an async modifier to the handler, but this caused an error with my return value - I can't return Connection.Broadcast(data) anymore.

This is what I got eventually:

protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
    // some EF code here..

    await db.SaveChangesAsync();

    // the original return changes into this?
    await Connection.Broadcast(data);
}

Is this the correct way to do this? Because I have a feeling I'm abusing the pattern.


btw, if I understand correctly, the current version of SignalR is fully asynchronous. Older versions like the one described here had two handlers - one synchronous and one asynchronous (with the Async postfix).

Was it helpful?

Solution

Yes, that is a perfectly reasonable way of going about it. Why do you feel that you may be abusing the pattern?

Think of it this way:

  • a synchronous method returning void corresponds to an async method returning Task. Likewise,
  • a synchronous method returning T corresponds to an async method returning Task<T>.

That is why you cannot do

protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
    return Connection.Broadcast(data);
}

, since the async keyword and your return statement would indicate a method that returned Task<Task>.

What you can do is remove your last await altogether. All it will do is create an empty continuation (because it is essentially saying "when the broadcast is done, run the code after the broadcast and until the ending curly brace"). Or you can leave it in for consistency, if you prefer.

OTHER TIPS

I have an async intro on my blog that you may find helpful.

A Task instance represents a "future". So when you're doing this:

protected override Task OnReceived(IRequest request, string connectionId, string data)
{
  return Connection.Broadcast(data);
}

You're saying "OnReceived is done when Connection.Broadcast(data) is done". This is practically the same as:

protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
  await Connection.Broadcast(data);
}

Which is saying "OnReceived will (asynchronously) wait for Connection.Broadcast(data) to be done, and then OnReceived will be done." It's slightly more efficient without the async and await, but they have practically the same semantics.

So, yes, this code would be correct:

protected override async Task OnReceived(IRequest request, string connectionId, string data)
{
  // some EF code here..
  await db.SaveChangesAsync();

  await Connection.Broadcast(data);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top