LostBeard / SpawnDev.BlazorJS

Full Blazor WebAssembly and Javascript Interop with multithreading via WebWorkers
https://blazorjs.spawndev.com
MIT License
78 stars 6 forks source link

Task Cancellation #2

Closed rachied closed 1 year ago

rachied commented 1 year ago

Hello,

First of all, thank you for making such an awesome library. I was disappointed by the lack of .NET 7 support in BlazorWorker, so I'm glad to have stumbled upon yours.

I am trying to utilize web workers to execute some long running tasks (running dynamically compiled code / unit tests), and some of them may run infinitely long. Which is why I am searching for some way to terminate tasks after a certain period of time.

Using BlazorJS.WebWorkers I was able to offload the compilation and execution of the tests to a WebWorker, so the browser UI no longer hangs. And by chaining WaitAsync to the Task that is returned by the WebWorker and awaiting that, it will throw a TimeOutException when the Task exceeds the timeout that has been set. All good so far!

However, the WebWorker continues executing the Task until completion, and I'd like for a way to stop it from doing that.. I do not control all the inner workings of my test runner, so I am unable to e.g. send over a CancellationToken and check that continuously. Do you have any idea how to achieve this?

Here's a code snippet for reference:

public async Task CompileAndRun()
{
    var code = await Editor.GetValue();

    _webWorker ??= await WebWorkerService.GetWebWorker();

    var runner = _webWorker.GetService<ITestWorker>();

    try
    {
        var result = await runner
            .RunTests(code)
            .WaitAsync(TimeSpan.FromSeconds(5));

        if (result.TestCount == 0)
        {
            await Alert("An unexpected error occurred");
            return;
        }

        if (result.FailedCount > 0)
        {
            await Alert("One or more tests failed");
        }
        else
        {
            await Alert("All tests passed, nice!");
        }
    }
    catch (TimeoutException e)
    {
        await Alert("Test suite timed out");
    }
    finally
    {
        _webWorker?.Dispose();
        _webWorker = null;
        // In case of timeout, WebWorker is still doing work at this point..
    }
}
rachied commented 1 year ago

I guess Worker.terminate() is what I'm looking for. I don't think we have a binding for that yet (or I didn't look in the right places)

LostBeard commented 1 year ago

Calling Dispose on the WebWorker calls the underlying worker's terminate method. I just tested on the demo WebWorker page and clicking the Dispose button, while the worker is working, stops it and kills the worker. I'm not sure why it is not doing the same for your tests.

rachied commented 1 year ago

You are correct. It's my setup that was wrong. Those await Alert() calls invoked the JS alert, which blocked the main thread and allowed the web worker to continue on a bit past the timeout period prior to being disposed - leading me to believe it wasn't being terminated.