Skip to content

JSAsyncScope structure

Helps preventing Node.JS process from exiting while we execute C# async functions.

C#
public struct JSAsyncScope : IDisposable

Public Members

namedescription
JSAsyncScope()The default constructor.
IsDisposed { get; }
Dispose()

Remarks

Async function uses await keyword that splits it up into several sequentially executed tasks. Each task can run in its own task scheduler. For running tasks in Node.JS main loop we use JSSynchronizationContext. In the following code we start and end async function execution in JSSynchornizationContext if we start running it in the main loop thread:

C#
public async void RunAsyncTask(JSDeferred deferred, int id)
{
    // Capture current SynchornizationContext and run work in a background thread.
    var data = await RetriveDataById(id);

    // After exiting await we use captured SynchornizationContext to run remaining code in that context.
    deferred.Resolve((JSValue)data.FullName);
}

The work in the background thread may take some time and Node.JS process can finish because it completed all current tasks. It is not aware about us running important code in the background thread. We must say to Node.JS that we plan to do some important work in its main loop after we finish the background task. The OpenAsyncScope and CloseAsyncScope can be used to do that:

C#
public async void RunAsyncTask(JSDeferred deferred, int id)
{
    // Ask Node.JS to keep process alive because we need its main loop.
    JSSynchronizationContext.Current.OpenAsyncScope();

    var data = await RetriveDataById(id);
    deferred.Resolve((JSValue)data.FullName);

    // Tell Node.JS that we finished using its main loop and Node.JS process can exit
    // after completing current callback.
    JSSynchronizationContext.Current.CloseAsyncScope();
}

Note that these two functions must be called in the main loop thread. The JSAsyncScope is a convenience struct that calls these two functions for us automatically in it constructor and Dispose method. We can rewrite the code above as:

C#
public async void RunAsyncTask(JSDeferred deferred, int id)
{
    // We must use 'using' keyword to call 'Dispose' in the end.
    using var asyncScope = new JSAsyncScope();

    var data = await RetriveDataById(id);
    deferred.Resolve((JSValue)data.FullName);
}

See Also

Released under the MIT license