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.

1 Like