Making async calls from RunCommand

Hi all,

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

You need to await the asynchronous operation.

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.

Thanks for suggestion but this way It never gets complete unfortunately :frowning:

Hi Steve,
This is what I tried so far:

// 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?

Have you tried

Task<List<string>> hubTask = HubConnection.InvokeAsync<List<string>>("GetProjectNames"));
List<string> results = hubTask.Result;

The working solution:

List<string> results = Task.Run(() => HubConnection.InvokeAsync<List<string>>("GetProjectNames")).GetAwaiter().GetResult();

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.

usefull links:
https://docs.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development#transform-synchronous-to-asynchronous-code

2 Likes

This is a life saver. Thanks for sharing!