相关文章推荐

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

This is what I tried so far:

var projects = Task.Run(async () => await HubConnection.InvokeAsync<List<string>>("GetProjectNames")).Result; var projects = Task.Run(async () => await HubConnection.InvokeAsync<List<string>>("GetProjectNames")).GetAwaiter().GetResult(); 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

 
推荐文章