Tasks, Async & Await in Grasshopper

Hi all (@DavidRutten)
I’m wondering if it’s possible to use async and await in Grasshopper. Consider the following isolated test:

using System.Threading.Tasks;

private void RunScript(bool go, ref object res)
  {
    if (go) {
      Waiter(100);
      res = "Successfully waited";
    }
  }

    async Task Waiter(int millis) {
         await Task.Delay(TimeSpan.FromMilliseconds(millis))
            .ContinueWith((t) => {
                 Rhino.RhinoApp.WriteLine("Task complete");
  });
}

As expected, this will wait a few milliseconds, then write to Rhino’s console to notify that the task has completed.

However, modifying it to Waiter(100).Wait()) (which would normally just cause the Task to execute synchronously) never appears to return, hence Grasshopper/Rhino hangs. It doesn’t throw any exceptions and attaching a debugger to Rhino doesn’t yield any insight.

It seems that the thread executes, but never yields, hence the UI stall.

Now, I’m not sure if I’ve got my .net versioning mixed up (understanding that Rhino/GH is net45) or the details of how the ScriptInstance compiler works but I expected this to work out of the box given that the intellisense is picking up the async and await keyboards to begin with (C# 5+).

Any insight or suggestions?

io_await.gh (5.1 KB)

Side note: This still hangs if the WriteLine is removed, so I don’t believe there to be any issues with thread-crossing.

Interestingly, this one works fine and demonstrates expected behaviour.

if (go) {
  Task t = Task.Factory.StartNew(() => {
    Thread.Sleep(1000);
    Rhino.RhinoApp.WriteLine("Complete");
    });

  t.Wait();
}

Similarly, this works:

private void RunScript(bool go, ref object res)
  {
    if (go) {
      DoTask();
    }
  }

  // <Custom additional code> 
  async Task DoTask() {
    Task t = Task.Factory.StartNew(() => {
      Thread.Sleep(1000);
      Rhino.RhinoApp.WriteLine("Complete");
      });

    await t;
  }
  // </Custom additional code> 

but using DoTask().Wait(); ends up ‘hanging’.

What’s also interesting is that it still prints and displays in the Rhino command line, suggesting that it completes properly and Rhino is running, but GH is still holding control / doing something. Another tell would be that the UI doesn’t show as normal windows ‘not responding’, but the windows UI elements flicker:

async_control

CPU profiling shows that the Wait() call is still running:
image

But I can’t figure out how that works - because Rhino still prints the message to console, which would suggest that Wait() has returned, the GH solution is finished and Rhino’s UI thread is not blocked…

So after some further research using async/await in this context is causing a deadlock. For posterity, there is a good article on this here.

Subsequently, waiting on a contextless awaiter solves this issue, and the following code blocks work:

  private void RunScript(bool go, ref object res)
  {
    if (go) {
      DoTask().Wait();
    }
  }

  // <Custom additional code> 
  async Task DoTask() {
    var t = Task.Factory.StartNew(() => {
      Thread.Sleep(1000);
      Rhino.RhinoApp.WriteLine("Complete");
      }).ConfigureAwait(false);

    await t;
  }
  // </Custom additional code> 

This can be considered solved.

7 Likes

for reference, you can use async workarround to make an async component like this one

Hi Ivan, sorry for a basic question. Where can I find the GH component on your Github page? I’m having a problem using a Python component which loads for 10s each time. And finding a solution to make the Rhino/GH not freezing while the component is loading. Thanks in advance!

Hi @Cheung_Henrik
The goal of this was slightly different to not freezing the definition (in fact, it was supposed to - the goal was to allow using async syntax in script components.

What you’ll need to do is slightly different - I don’t use ghpython much so someone else can chime in here with the syntax, but essentially:

  1. When your component starts updating, store a local state i.e. “isUpdating” and let the solution end. Output some placeholder data or a temporary state
  2. When your long asynchronous task finishes, schedule a new solution and expire your component during the solution. When your component updates this time, set “isUpdating” to false and output the results.
1 Like

Thank Cameron for the explanation. Although I at last resolved by Solution Async plugin, your explanation helped me to understand the logic behind (I suppose the principle is similar)!

And also it appears to me that C# seems to have more potential, if we want to work with lower levels programming in Rhino/Grasshopper.