首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >WebAssembly CancellationToken on JSRuntime

WebAssembly CancellationToken on JSRuntime
EN

Stack Overflow用户
提问于 2021-09-09 14:11:02
回答 1查看 689关注 0票数 0

我试图取消一个长期运行的JavaScript进程时,用户导航离开一个页面在WebAssembly。我在页面上实现IDisposable,并在调用JavaScript函数时传递一个令牌。

在Dispose方法上,我在令牌上调用.Cancel()和.Dispose()。但是,如果我启动该函数并导航到另一个页面,则该过程仍将继续。

下面是一些示例代码来显示我遇到的问题。这是使用基本的Blazor WebAssembly模板。

如果打开控制台窗口,您将看到,一旦到达“计数器”页面,我将在OnInitializedAsync()上调用OnInitializedAsync方法,那么如果您使用菜单导航到另一个页面,如"Home“,并等待超时被调用,它仍将打印到控制台日志中。

这里是我的JavaScript:

代码语言:javascript
复制
(function () {
    testFunctions = {
        testFunction1: function (arguments) {

            var message = arguments[0];
            var timeOutLength = arguments[1];

            setTimeout(function () {
                console.log(message);
            }, timeOutLength);

        }
    }
})();

和Razor页面:

代码语言:javascript
复制
@page "/counter"
@inject IJSRuntime _jsRuntime
@implements IDisposable



@code {

    private System.Threading.CancellationTokenSource jsCancellationToken = new System.Threading.CancellationTokenSource();

    protected override async Task OnInitializedAsync()
    {
        var args = new List<object>();
        args.Add("Hello!");
        args.Add(10000);

        var uploadResult = await _jsRuntime.InvokeAsync<string>("testFunctions.testFunction1", jsCancellationToken.Token, args);

    }

    public void Dispose()
    {
        jsCancellationToken.Cancel();
        jsCancellationToken.Dispose();
    }

}

我的问题是,当我释放页面时,我怎样才能完全停止继续运行这个JavaScript进程呢?

EN

回答 1

Stack Overflow用户

发布于 2021-09-10 08:55:03

从注释中可以看出,长操作正在下载一个2GB文件。由于Blazor无法写入磁盘,也无法将.NET流发送到Javascript (然而,相反的可能是在Blazor 6预览7),这意味着我们需要让JavaScript管理整个下载。

在Javascript中的中止

Javascript中的中止/取消是由AbortController类提供的,它的工作方式类似于CancellationTokenSource。它通过调用AbortSignal方法时发出信号的signal属性提供中止

文档示例是相关的,因为它显示了取消使用fetch的长fetch(url, {signal})操作。

代码语言:javascript
复制
var controller = new AbortController();
var signal = controller.signal;

var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');

downloadBtn.addEventListener('click', fetchVideo);

abortBtn.addEventListener('click', function() {
  controller.abort();
  console.log('Download aborted');
});

function fetchVideo() {
  ...
  fetch(url, {signal}).then(function(response) {
    ...
  }).catch(function(e) {
   reports.textContent = 'Download error: ' + e.message;
  })
}

从中止

问题是如何从abort调用.NET,如果脚本作为一个模块加载,就会变得很容易,如JavaScript模块中的JavaScript隔离部分所示。在这种情况下,我们可以将一个abort方法添加到调用AbortControllerabort的模块中。

基于文档的警告空气程序设计

代码语言:javascript
复制
var controller = new AbortController();
var signal = controller.signal;

abortDownload() {
  controller.abort();
  console.log('Download aborting');
});

function download(url,filenamme) {
  ...
  fetch(url, {signal})
    .then( res => res.blob() )
    .then( blob => {
        const file = window.URL.createObjectURL(blob);
        const a = document.createElement("a");
        document.body.appendChild(a);
        a.href = exportUrl;
        a.download = filename;
        a.target = "_self";
        a.click();
        URL.revokeObjectURL(exportUrl);
    ...
  }).catch(function(e) {
   console.log('Download error: ' + e.message);
  })
}

脚本和模块通常在第一次呈现之后导入,以确保页面已经呈现,所有Javascript调用都将成功。这在这里并不是绝对必要的:

代码语言:javascript
复制
@implements IAsyncDisposable
...
private IJSObjectReference module;
private CancellationTokenSource cts = new CancellationTokenSource();

protected override async Task OnInitializedAsync()
{
    module = await _jsRuntime.InvokeAsync<IJSObjectReference>("import", 
            "./scripts.js");
    await module.InvokeVoidAsync("download", cts.Token, url,filename);
    
}

private async Task AbortAsync()
{
    //In case we navigate away before the module is loaded
    if(module!=null)
    {
        await module.InvokeVoidAsync("abortDownload");
    }
}

在处理时,必须先调用AbortAsync以取消fetch,然后调用CancelationTokenSource信令:

代码语言:javascript
复制
public async ValueTask DisposeAsync()
{
    await AbortAsync();
    cts.Cancel();
    cts.Dispose();
}

所有这些异步杂耍都是必要的,因为使用了IJSRuntime,它只提供异步操作。这是有意义的,特别是在Blazor中,Javascript调用必须发送到客户端浏览器执行。

Blazor项目可以使用IJSInProcessRuntime,它提供同步和异步方法。同步方法实际上比异步方法更快。

在这种情况下,AbortDispose可以保持同步:

代码语言:javascript
复制
@inject IJSInProcessRuntime _jsRuntime
@implements IDisposable
...
private void  Abort()
{
    //In case we navigate away before the module is loaded
    if(module!=null)
    {
        module.InvokeVoid("abortDownload");
    }
}

public void Dispose()
{
    Abort();
    cts.Cancel();
    cts.Dispose();
}

此外,可以使用CancellationToken.Register调用Abort

代码语言:javascript
复制
protected override async Task OnInitializedAsync()
{
    module = await _jsRuntime.InvokeAsync<IJSObjectReference>("import", 
            "./scripts.js");

    var ct=cts.Token;
    ct.Register(()=>Abort());

    await module.InvokeVoidAsync("download", ct, url,filename);
    
}

public void Dispose()

    cts.Cancel();
    cts.Dispose();
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69119763

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档