我试图取消一个长期运行的JavaScript进程时,用户导航离开一个页面在WebAssembly。我在页面上实现IDisposable,并在调用JavaScript函数时传递一个令牌。
在Dispose方法上,我在令牌上调用.Cancel()和.Dispose()。但是,如果我启动该函数并导航到另一个页面,则该过程仍将继续。
下面是一些示例代码来显示我遇到的问题。这是使用基本的Blazor WebAssembly模板。
如果打开控制台窗口,您将看到,一旦到达“计数器”页面,我将在OnInitializedAsync()上调用OnInitializedAsync方法,那么如果您使用菜单导航到另一个页面,如"Home“,并等待超时被调用,它仍将打印到控制台日志中。
这里是我的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进程呢?
发布于 2021-09-10 08:55:03
从注释中可以看出,长操作正在下载一个2GB文件。由于Blazor无法写入磁盘,也无法将.NET流发送到Javascript (然而,相反的可能是在Blazor 6预览7),这意味着我们需要让JavaScript管理整个下载。
在Javascript中的中止
Javascript中的中止/取消是由AbortController类提供的,它的工作方式类似于CancellationTokenSource。它通过调用AbortSignal方法时发出信号的signal属性提供中止。
文档示例是相关的,因为它显示了取消使用fetch的长fetch(url, {signal})操作。
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方法添加到调用AbortController的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中,Javascript调用必须发送到客户端浏览器执行。
Blazor项目可以使用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();
}https://stackoverflow.com/questions/69119763
复制相似问题