【问题标题】:Blazor WebAssembly CancellationToken on JSRuntimeJSRuntime 上的 Blazor WebAssembly CancellationToken
【发布时间】:2021-11-06 05:06:39
【问题描述】:

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

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

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

如果您打开控制台窗口,您会看到,一旦您到达“计数器”页面,我会在 OnInitializedAsync() 上调用 JavaScript 方法,然后如果您使用菜单导航到另一个页面,例如“主页”并等待被调用的超时仍然会打印到控制台日志。

这是我的 JavaScript:

(function () {
    testFunctions = {
        testFunction1: function (arguments) {

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

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

        }
    }
})();

还有 Razor 页面:

@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 进程继续运行?

【问题讨论】:

  • 我认为您需要调用 clearTimeout,这意味着您需要存储从 setTimeout 返回的值 - 然后调用另一个函数 cancelTestFunction1 来取消。
  • A CancellationToken 不会中止呼叫。它用于协作取消 - 向另一种方法发出取消信号。由方法来优雅地停止和退出。在这种情况下,取消将取消对 InvokeAsync 的等待,但没有什么可以告诉 JavaScript 方法它需要退出
  • @INNVTV,按照 Magoo 先生的建议,使用 clearTimeout 函数。实现代码的最简单方法是使用 DotNetObjectReference 对象。
  • @PanagiotisKanavos 如何从我的 JavaScript 访问 CancellationToken?它似乎没有作为参数传入。
  • @enet 我仅使用 setTimeout() 作为示例运行过程。在现实世界的场景中(在我的情况下,使用块上传大文件)我们可能没有方法。

标签: javascript .net asp.net-core blazor blazor-webassembly


【解决方案1】:

从 cmets 看来,长操作正在下载一个 2GB 的文件。由于 Blazor 无法写入磁盘,也无法将 .NET Stream 发送到 Javascript(然而,反过来in Blazor 6 Preview 7),这意味着我们需要让 JavaScript 管理整个下载。

在 Javascript 中中止

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

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

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;
  })
}

从 Blazor 中止

问题变成了如何从.NET 调用abort。如果将脚本作为模块 加载,这将变得很容易,如JavaScript isolation in JavaScript modules 部分所示。在这种情况下,我们可以向调用AbortController 的abort 的模块添加一个abort 方法。

警告基于文档的空中编程

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 调用都会成功。这不是绝对必要的:

@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:

public async ValueTask DisposeAsync()
{
    await AbortAsync();
    cts.Cancel();
    cts.Dispose();
}

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

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

在这种情况下,Abort 和 Dispose 可以保持同步:

@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:

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();
}

【讨论】:

    猜你喜欢
    • 2020-08-24
    • 2020-12-23
    • 2020-09-28
    • 2021-01-21
    • 1970-01-01
    • 2020-10-11
    • 2020-08-19
    • 2020-06-26
    • 1970-01-01
    相关资源
    最近更新 更多