My plugin has a SignalR Core client connected to the server. When a user runs a command I want to retrieve some options from the server. The problem is that SingalR Core library allows only async calls to the server and I need to make it from the perfectly synchronous RunCommand method. So I do some bad practice hacking along these lines:
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
var options = HubConnection.InvokeAsync<List<string>>("GetProjectNames").Result;
var go = new GetOption();
...
}
I would get away with this on .Net Core but .Net Framework makes Rhino freeze for a while and then windows show some nasty alert message. I believe this is what smart people on the internet call the deadlock.
Does anyone know a safe way to make such async calls from Rhino?
Dear @archimax - just a (really) quick guess, not knowing SignalR Core Library:
shouldn’t you wait (sleep) until the Task / Operation (InvokeAsync…returns something like this i guess) is completed - before you try to get the Result.
dummy code similar to:
var operation = HubConnection.InvokeAsync<List<string>>("GetProjectNames");
Rhino.RhinoApp.WriteLine("Waiting for ProjectNames");
// check and sleep... pseudocode
while (!operation.IsComplete)
{
Thread.Sleep(100);
// handle fails pseudocode
if (operation.failed) return Result.Failure;
}
var options = operation.Result;
var go = new GetOption();
...
but sorry if i am totally wrong…kind regards,tom
Calling Result on a Task will force the active thread to wait until the task is complete. There are other Task wait functions that you can call to potentially do this in a cleaner manner.
// 1
var projects = Task.Run(async () => await HubConnection.InvokeAsync<List<string>>("GetProjectNames")).Result;
// 2
var projects = Task.Run(async () => await HubConnection.InvokeAsync<List<string>>("GetProjectNames")).GetAwaiter().GetResult();
// 3
var projects = HubConnection.InvokeAsync<List<string>>("GetProjectNames").GetAwaiter().GetResult();
// 4 with ConfigureAwait(false)
var projectsTask = HubConnection.InvokeAsync<List<string>>("GetProjectNames").ConfigureAwait(false);
var projects = projectsTask.GetAwaiter().GetResult();
// 5 with and without TaskContinuationOptions
var projectsTask = HubConnection.InvokeAsync<List<string>>("GetProjectNames");
projectsTask.ContinueWith(x =>
{
projects = x.GetAwaiter().GetResult();
}, TaskContinuationOptions.ExecuteSynchronously).GetAwaiter().GetResult();
// 6 forcing to run on UIThread
var projectsTask = HubConnection.InvokeAsync<List<string>>("GetProjectNames");
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
projectsTask.ContinueWith(x =>
{
projects = x.Result;
}, uiContext).Wait();
All of them cause the deadlock.
I wonder how it is done inside Grasshopper Hops component? I assume it also runs some kind of async tasks from a synchronous grasshopper code, does it?
Task.Run() runs the async method on a new thread not blocking Rhino UI thread. But it is important that every async call from RhinoCommands has to be done like this.
My problem was that I used hubConnection.StartAsync().Result to start a connection without wrapping it with Task.Run(). It worked pretty well on async void methods like hubConnection.SendAsync(...) so I didn’t notice anything wrong. But when I tried to call a return value method hubConnection.InvokeAsync(...) it blocked the UI. Seems like something inside hubConnection object “stuck” to the UI thread and didn’t allow to start the process on a new thread even when I used Task.Run() for the following methods.