SignalR An asynchronous operation error - asp.net

I'm using SignalR 1.1.2 and I have problem with async hub method. Everything works fine on my PC with ForeverFrame transport but after deploying on server and switching to web sockets transport I receive following error:
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>.
My hub method code:
public async Task<string> getUrl()
{
var url = await MyWebservice.GetMyRoomUrlAsync(Context.User.Identity.Name);
return url;
}
Are async methods supported in SignalR with web-sockets transport?
Update:
GetMyRoomUrlAsync code:
public static Task<string> GetMyRoomUrlAsync(string email)
{
var tcs = new TaskCompletionSource<string>();
var client = new Onif40.VisualStudioGeneratedSoapClient();
client.GetRoomUrlCompleted += (s, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.GetRoomUrlAsync(email);
return tcs.Task;
}
After Stephen Cleary clarified me where the problem was, solving it by rewriting EAP to APM was trivial.
public static Task<string> GetMyRoomUrlAsync(string email)
{
var tcs = new TaskCompletionSource<string>();
var client = new Onif40.VisualStudioGeneratedSoapClient();
client.BeginGetRoomUrl(email, iar =>
{
try
{
tcs.TrySetResult(client.EndGetRoomUrl(iar));
}
catch (Exception e)
{
tcs.TrySetException(e);
}
}, null);
return tcs.Task;
}

async methods are supported. However, you cannot use async void or async wrappers around EAP methods.
One common cause of this is using WebClient instead of the newer HttpClient. If that's not the case here, you would need to post the implementation of GetMyRoomUrlAsync.

Related

Azure Function SignalR Negotiate function works but Send function fails

i have a xamarin app that is trying to talk to use SignalR in Azure functions.
i have 2 azure functions as per the documentation.
public static class NegotiateFunction
{
[FunctionName("negotiate")]
public static SignalRConnectionInfo GetSignalRInfo(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
//, UserId = "{headers.x-ms-client-principal-id}"
{
return connectionInfo;
}
}
and
public static class SendMessageFunction
{
[FunctionName("Send")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
// var chatObj = (ChatObject)(message);
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
// UserId = chatObj.ReciversId,
Target = "Send",
Arguments = new[] { message }
});
}
}
in my xamarin client i am connecting like this.
try
{
_connection = new HubConnectionBuilder()
.WithUrl("http://192.168.1.66:7071/api")
.Build();
_connection.On<string>("Send", (message) =>
{
AppendMessage(message);
});
await _connection.StartAsync();
}
I send message using this code in one of the pages of Xamarin app page.
try
{
await _connection.SendAsync("Send", MessageEntry.Text);
MessageEntry.Text = "";
}
connection code works it hits "negotiate" function properly but when i call SendAsync it does not hit break-point in [FunctionName("Send")] and nothing happens. It doesn't give me any exception as well.
local settings are like this
Update
i also tried Invoke. it didnt worked.
Should i try making a POST call to [FunctionName("Send")] ?
The way SignalR SaaS works in Functions is slightly different to using the NuGet package in a .NET Application.
You can't invoke a function using the SignalR library, as you can see on the attribute in your function, it's expecting a Http trigger so you have to do a POST to this endpoint instead of invoking it as you normally would.
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
You still want to listen to the Send target as normal.

Returning from an async method in a SignalR Core hub

I have an ASP .Net Core 2.2 Web API. In it I am using SignalR. In my hub, I need to save messages to the database, so I am getting an instance of DbContext, and because I am calling the SaveChangesAsync() method of DbContext, I need to make the method async. So, from
public Task SendMessageToAll(string message)
{
return Clients.All.SendAsync("ReceiveMessage", message);
}
I now have
public async Task SendMessageToAll(string message)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<DbContext>();
Message newMessage = new Message()
{
Body = message,
Timestamp = DateTime.Now
};
dbContext.Messages.Add(newMessage);
await dbContext.SaveChangesAsync();
}
return Clients.All.SendAsync("ReceiveMessage", message);
}
However, now I'm getting this error:
Since 'ChatHub.SendMessageToAll(string)' is an async method that returns 'Task', a return keyword must not be followed by an object expression. Did you intend to return 'Task'?
Which makes sense, but I'm not sure how to fix it. Any ideas?
Change your code as below:
return Clients.All.SendAsync("ReceiveMessage", message);
await Clients.All.SendAsync("ReceiveMessage", message);

An asynchronous operation exceeded the page timeout (trying to use HttpRequestMessage with ASP.NET WebForms page)

I have created a web API in AWS that I am trying to get some JSON back from using a web page built in ASP.NET Webforms (most up-to-date version). I can't get the asynchronous part to work. Either the GET method hangs seemingly forever, or - following the best practice approach from the Microsoft documentation - I get this error after a little while:
[TimeoutException: An asynchronous operation exceeded the page
timeout.] System.Web.UI.d__554.MoveNext() +984
I know this is something to do with the wait/async portion of the code and being in ASP.NET because of the following.
If I use very similar code in a console application it works fine.
If i call the web API using POSTMAN it works fine.
I have made async = true in the page directive. Here is my page load
protected void Page_Load(object sender, EventArgs e)
{
try
{
RegisterAsyncTask(new PageAsyncTask(GetStuffAsync));
}
catch (Exception ex)
{
renderStoreCards.Text = ex.Message;
}
}
Here is my method
private async Task GetStuffAsync()
{
string testHtml = string.Empty;
try
{
var signer = new AWS4RequestSigner("AccessKey", "SecretKey");
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://some-aws-address-changed-for-stack-overflow.execute-api.ap-southeast-2.amazonaws.com/Prod/tables/InSiteStoreInformation/ServerName")
};
request = await signer.Sign(request, "execute-api", "ap-southeast-2");
var client = new HttpClient();
var response = await client.SendAsync(request).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
renderStoreCards.Text = ex.Message;
}
}
The above example produces a TimeoutException. Previous to the above, I was trying the following code. This works fine in a console app, but not in the ASP.NET page.
class Program
{
static void Main(string[] args)
{
try
{
MainAsync().Wait();
}
catch (Exception ex)
{
Console.WriteLine($"Exception occured {ex.Message}");
Console.ReadKey();
}
}
static async Task MainAsync()
{
try
{
var signer = new AWS4RequestSigner("AccessKey", "SecretKey");
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://<Hiddenforstackoverflowpost>.execute-api.ap-southeast-2.amazonaws.com/Prod/tables/InSiteStoreInformation/ServerName")
};
request = await signer.Sign(request, "execute-api", "ap-southeast-2");
var client = new HttpClient();
var response = await client.SendAsync(request);
var responseStr = await response.Content.ReadAsStringAsync();
dynamic sales = Newtonsoft.Json.JsonConvert.DeserializeObject(responseStr);
Console.WriteLine($"Server = {sales[0].ServerName}");
Console.ReadKey();
Console.Write(responseStr);
Console.ReadKey();
}
catch (Exception ex)
{
throw (ex);
}
}
}
I am by no means an expert in async/wait combinations, but it appears that the HttpClient I'm using has no synchronous alternative, so I have to figure this out.
I know this is an old question, but I just came across.
The timeouts are most likely caused by ReadAsStringAsync as you neglected to use '.ConfigureAwait(false)' on it. Running async tasks inside ASP.net can be very finicky especially around execution contexts. It most likely is some kind of dead-lock on the async method when it tries to restore the execution context on its return. This usually fails due to the nature of IIS hosting. I am not sure if it actually is a dead-lock or some other issue under the hood. Just make sure to always use .ConfigureAwait(false).
I know this is old, but maybe it helps someone else coming across this issue.

How I can safely run async code, when handling messages from Messenger?

I am using MVVM, inparticular MVVMLight. For boradcasting to all of my modelviews, that no internet connection is available I am using Messenger class. The modelviews subscribe to this event in order to reload itself with offline data, inform user etc.
However, I have a problem. When I have the folowing handler:
private void HandleNoInternetMessage(NoInternetAccessMessage obj)
{
Task.Run(async () => await InitializeForOfflineInternalAsync());
}
public async Task InitializeForOfflineInternalAsync()
{
try
{
WaitingLayerViewModel.ShouldBeVisible = true;
WaitingLayerViewModel.IsBusy = true; //<--exception HRESULT: 0x8001010E (RPC_E_WRONG_THREAD)
bool switchToOffline = await CommonViewModelProvider.InformUserOfNoInternetAccessAndChangeAppState(); //<!- CoreWindow.GetForCurrentThread().Dispatcher is null
await FilterTestItemViewModel.InitializeForOfflineAsync();
await FilterTestItemViewModel.InitializeForOfflineAsync();
WaitingLayerViewModel.ShouldBeVisible = false;
WaitingLayerViewModel.IsBusy = false;
...
}
}
I got exception HRESULT: 0x8001010E (RPC_E_WRONG_THREAD), because in InitializeForOfflineInternalAsync I am changing some properties of the viewmodel wchich are bound in XAML (or at least I think it is because of that). However, it is weird, because I am changing in other code bound properties regularly and have no problems with it (and the thread is a working thread).
Now, how can i solve that?
The messanger let me provide only delegate which is not async (which make kind of sense), so I can not have the HandleNoInternetMessage method async
I am using async await ... no explicit spawning of threads
I dont have access in VM to Dispatcher, because I am in VM which should not know about platform dependent stuff. And when I tried to use it to show a message, NullPointer excpetion was thrown when calling CoreWindow.GetForCurrentThread().Dispatcher; And again when calling from other places, no such exception was thrown
I guess the question is How I can safely run async code, which changes boudn properties, when handling messages from Messenger?
You're responding to messages that are logically events, so this is an acceptable use case for async void.
private async void HandleNoInternetMessage(NoInternetAccessMessage obj)
{
await InitializeForOfflineInternalAsync();
}
public async Task InitializeForOfflineInternalAsync()
{
try
{
WaitingLayerViewModel.ShouldBeVisible = true;
WaitingLayerViewModel.IsBusy = true;
bool switchToOffline = await CommonViewModelProvider.InformUserOfNoInternetAccessAndChangeAppState();
await FilterTestItemViewModel.InitializeForOfflineAsync();
await FilterTestItemViewModel.InitializeForOfflineAsync();
WaitingLayerViewModel.ShouldBeVisible = false;
WaitingLayerViewModel.IsBusy = false;
...
}
}
Remember that Task.Run is for CPU-bound code (as I describe on my blog).

How to invoke a post when using HubController<T>?

I can't find much documentation on the new HubController<T> so maybe I'm going about this wrong. This is what I have:
public class StatusController : HubController<StatusHub>
{
private string _status = "";
public string Get()
{
return _status;
}
public void Post(string status)
{
_status = status;
// Call StatusChanged on SignalR clients listening to the StatusHub
Clients.All.StatusChanged(status);
}
}
public class StatusHub : Hub { }
This is how I'm attempting to create the hub proxy:
var hubConnection = new HubConnection("http://localhost:51076/");
var statusHubProxy = hubConnection.CreateHubProxy("StatusHub");
statusHubProxy.On<string>("StatusChanged", status => Console.WriteLine("New Status: {0}", status));
await hubConnection.Start();
How do I call the Post method of my controller? This is where I'm getting an exception:
await statusHubProxy.Invoke("Post", "Test Status");
HubController<T> just provides some basic plumbing that gets you access to the resources that are associated with the specific hub type (e.g. Clients) that you want to work with. Calling it has nothing to do with invoking the actual hub itself, so you don't use the hub client API, it's just straight HTTP calls. Without HubController<T> you would have to reach out to SignalR's GlobalHost.Configuration.GetHubContext<T>() yourself to find the IHubContext for your hub type.
So, you can call your StatusController::Post method with any of the standard .NET HTTP APIs: HttpClient, WebClient or HttpWebRequest.

Resources