JS 互操作导致 Blazor 中的线程锁定

JS Interop causing thread-lock in Blazor

提问人:Chris Phillips 提问时间:11/17/2023 更新时间:11/18/2023 访问量:38

问:

我有一个垂直的项目列表,其中 draggable=“true”。当 A 最初被拖动到 B [ondragenter] 上时,将调用 DoJsThingy()。DoJsThingy 使用 IJsObjectReference.InvokeVoidAsync() 调用 javascript 函数。

当我将 div A 悬停在 div B 上时,会显示警报。当警报关闭时,鼠标指针仍处于 div A 的悬停样式,并且应用冻结。

奇怪的行为是 JS 方法执行并返回得很好。我通过在它之后抛出一个简单的 WriteLine() 来验证这一点。InvokeVoidAsync 之后的日志消息正在工作,但似乎从未调用过 DisposeAsync()。如果我删除 JS 调用,应用程序不会在悬停事件后冻结。

MyComponent.razor

@namespace BlazorWASM.Shared

@inject IDataService _dataService
@inject IUserService _userService
@inject IJSRuntime JS
@implements IAsyncDisposable

<div class="sidebar-grid">
    <h2 class="data-models-header">Data Models</h2>
    <input type="text" placeholder="Search" @bind="@searchString" class="search-bar" @bind:event="oninput" @onkeyup="() => UpdateSearchResults()" />
    <div class="data-model-list">
        @foreach (var model in searchResults)
        {
            <div @ref="listItems[model.Id]" draggable="true" class="@StyleDataModelListItem(model)" @ondragenter="() => DoJsThingy(model.Id)" @onclick="() => SelectDataModel(model)">
                <div class="data-model-list-item-icon-container">
                    <img class="data-model-list-item-icon" src="Images/data_model.svg" />
                </div>
                <div class="data-model-list-item-text">@model.Name</div>
            </div>
        }
    </div>
    <div class="data-model-save-load-buttons">
        <button class="new-data-model-button" @onclick="() => CreateDataModel()">New Data Model</button>
        <button class="delete-data-model-button" @onclick="() => DELETEITALL()">BIG Red Button</button>
    </div>
</div>



@code {
    public Dictionary<string, ElementReference> listItems = [];
    public string data = "";
    public List<DataModel> dataModels = [];
    public List<DataModel> searchResults = [];
    public DataModel? SelectedDataModel;
    public string searchString = "";
    private IJSObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/test.js");
    }

    protected override void OnInitialized()
    {
        _dataService.GetDataModelsBehaviorSubject().Subscribe(dms => {
            dataModels = dms;
            foreach(var dm in dataModels)
            {
                if (!listItems.ContainsKey(dm.Id)) listItems.Add(dm.Id, new ElementReference());
            }
            UpdateSearchResults();
            StateHasChanged();
        });
        _dataService.GetSelectedDataModelBehaviorSubject().Subscribe(sdm =>
        {
            SelectedDataModel = sdm;
            StateHasChanged();
        });
        _dataService.GetFieldLabelUpdatesBehaviorSubject().Subscribe(label =>
        {
            StateHasChanged();
        });
    }

    public async void RegisterUser()
    {
        await _userService.RegisterUser();
    }

    public async void CreateDataModel()
    {
        await _dataService.CreateDataModelAsync();
    }

    public async void ListDataModels()
    {
        await _dataService.LoadDataModelsAsync();
    }

    public void SelectDataModel(DataModel dataModel)
    {
        _dataService.SelectDataModel(dataModel);
    }

    public async Task DoJsThingy(string dataModelId)
    {
        try
        {
            var hoveredDataModel = _dataService.GetDataModel(dataModelId);
            if (module != null && hoveredDataModel != null)
            {
                await module.InvokeVoidAsync("showAlert", hoveredDataModel.Id);
                Console.WriteLine("Alert shown for model: " + hoveredDataModel.Id);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error in DoJsThingy: " + ex.Message);
        }
    }

    public async ValueTask DisposeAsync()
    {
        if(module != null)
        {
            await module.DisposeAsync();
        }
    }

    public string StyleDataModelListItem(DataModel dataModel)
    {
        var style = "data-model-list-item";
        if(SelectDataModel != null)
        {
            if (SelectedDataModel == dataModel)
            {
                style = "data-model-list-item-selected";
            }
        }
        return style;
    }

    public async Task DELETEITALL()
    {
        await _dataService.DELETEITALL();
    }

    public void DataModelListItemDragOver(string id)
    {
        var elementRef = listItems[id];
    }

    public void UpdateSearchResults()
    {
        if (String.IsNullOrEmpty(searchString))
        {
            searchResults = dataModels;
        }
        else
        {
            searchResults = dataModels.Where(d => d.Name.Contains(searchString)).ToList();
        }
    }
}

测试.js

export function showAlert(message) {
    alert(message);
    return;
}
JavaScript C# .NET Blazor-WebAssembly

评论


答:

0赞 MrC aka Shaun Curtis 11/18/2023 #1

您的代码中会出现几个问题。

每次组件渲染时都要设置,这在拖动操作中是很多的。您要处理的那个不会是您创建的那个!module

该事件用于执行诸如在元素上移动时更改元素的背景颜色之类的操作。这不是掉落事件。ondragover

下面是一个演示,展示了 hoe 如何将各种事件与一些日志记录一起使用。运行它并查看日志以查看发生了什么。

@page "/"
@inject IJSRuntime JS
@implements IAsyncDisposable

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<div class="list-group">
    @foreach (var country in _countries)
    {
        <div class="list-group-item" draggable="true"
             @key="country"
             @ondragstart="@((e) => OnDragStart(e, country))"
             @ondragover:preventDefault="true"
             @ondrop:preventDefault="true"
             @ondragover="@((e) => OnDragOver(e, country))"
             @ondrop="@(() => DoJsThingy(country))"
             @onclick="Clicked">
            @country
        </div>

    }
</div>

@code {
    private IJSObjectReference? module;
    private string? _dragCountry;

    private List<string> _countries = new() { "France", "Uk", "Spain" };

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        // only load it once
        if (firstRender)
            module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/test.js");
    }

    private void OnDragStart(DragEventArgs e, string data)
    {
        Console.WriteLine($"Drag Start: {data}");

    }

    private void OnDragOver(DragEventArgs e, string data)
    {
        Console.WriteLine($"Drag Over: {data}");
    }

    public async Task DoJsThingy(string dataModelId)
    {
        try
        {
            if (module != null)
            {
                await module.InvokeVoidAsync("showAlert", dataModelId);
                Console.WriteLine("Alert shown for model: " + dataModelId);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error in DoJsThingy: " + ex.Message);
        }
    }

    private void Clicked()
    {
        Console.WriteLine("Clicked");
    }

    public async ValueTask DisposeAsync()
    {
        // this only gets called when the component is no long part of the render tree and thr Renderer disposes it
        if (module != null)
        {
            await module.DisposeAsync();
        }
    }
}

至于为什么代码锁定应用程序,它是尝试执行 JS 操作的事件数量。

评论

0赞 Chris Phillips 11/20/2023
因此,我的总体目标是指示掉落的物品将落在哪里。垂直项列表将在悬停在其上的项上方创建一个开放空间(通过将列表项容器的高度加倍,并将列表项放在容器的底部)。