Skip to content

Instantly share code, notes, and snippets.

@EnCey
Created November 3, 2022 09:04
Show Gist options
  • Save EnCey/f452e772d9f885e8d012173d27b60a5f to your computer and use it in GitHub Desktop.
Save EnCey/f452e772d9f885e8d012173d27b60a5f to your computer and use it in GitHub Desktop.
public sealed class AsyncContext
{
private readonly Engine _engine;
private readonly SemaphoreSlim _semaphore = new(1);
private List<Task> _promiseTasks = new(); // not thread-safe!
public AsyncContext(Engine engine) => _engine = engine;
public JsValue CreateTaskPromise<T>(Task<T> task,
Func<Engine, T, JsValue> resolveAction,
Func<Engine, Exception, JsValue> rejectAction)
{
var (promise, resolve, reject) = _engine.RegisterPromise();
_promiseTasks.Add(RunPromiseTaskAsync(task, resolveAction, rejectAction, resolve, reject));
return promise;
}
// basic async wrapper method around task
private async Task RunPromiseTaskAsync<T>(Task<T> task,
Func<Engine, T, JsValue> resolveAction,
Func<Engine, Exception, JsValue> rejectAction,
Action<JsValue> resolvePromise,
Action<JsValue> rejectPromise)
{
try
{
// run the actual task
var result = await task.ConfigureAwait(false);
// run the JS resolve delegate single-threaded
await RunOnEngineThread(() =>
{
// resolveAction = arbitrary C# code that returns a JsValue and receives the task result
resolvePromise(resolveAction(_engine, result));
}).ConfigureAwait(false);
}
catch (Exception ex)
{
await RunOnEngineThread(() => rejectPromise(rejectAction(_engine, ex))).ConfigureAwait(false);
}
}
// there's no "engine thread", just ensures that only 1 thread accesses _engine
private async Task RunOnEngineThread(Action action)
{
try
{
await _semaphore.WaitAsync().ConfigureAwait(false);
action();
}
finally
{
_semaphore.Release();
}
}
public async Task WhenAllPromisesAreSettled()
{
while (true)
{
// obtain list of tasks to await, use semaphore to ensure it's not modified concurrently
List<Task> promisesToSettle;
try
{
await _semaphore.WaitAsync().ConfigureAwait(false);
promisesToSettle = _promiseTasks;
_promiseTasks = new List<Task>();
}
finally
{
_semaphore.Release();
}
if (promisesToSettle.Count == 0) return;
// awaiting the tasks can result in new promises being created
await Task.WhenAll(promisesToSettle).ConfigureAwait(false);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment