gpt4 book ai didi

c# - Blazor 组件 : notify of collection-changed event causing thread collisions

转载 作者:行者123 更新时间:2023-12-04 10:45:03 26 4
gpt4 key购买 nike

我正在使用 .Net Core 3.0 开发 ASP.NET Core Blazor 应用程序(我知道 3.1,但由于 Mordac,我现在只能使用这个版本)。

我有一个多组件页面,其中一些组件需要访问相同的数据,并且需要在更新集合时全部更新。我一直在尝试使用基于 EventHandler 的回调,但它们几乎同时在自己的线程上被调用(如果我 understand correctly ),导致 .razor 组件中的回调尝试对上下文进行服务调用同时。

注意:我已经尝试让我的 DbContext 的生命周期是短暂的,但我仍然遇到了竞争条件。

我很可能让自己陷入了异步 blender 并且不知道如何摆脱。

我初步断定 event EventHandler方法在这里不起作用。我需要一些方法来触发对组件的“集合更改”更新,而不会触发竞争条件。

我考虑过使用以下内容更新这些竞争条件中涉及的服务:

  • 将每个搜索功能替换为可公开绑定(bind)的集合属性
  • 让每个创建/更新/删除调用更新这些集合中的每一个

  • 这将允许组件直接绑定(bind)到已更改的集合,我认为这将导致任何组件中对其的每个绑定(bind)都更新而无需明确告知,这反过来又将允许我放弃“集合已更改"完全的事件处理。

    但我犹豫是否要尝试这个并且还没有这样做,因为它会在每个主要服务功能上引入相当多的开销。

    其他想法? 请帮忙。如果集合已更改,我希望依赖该集合的 Blazor 组件能够以某种方式更新,无论是通过通知、绑定(bind)还是其他方式。

    以下代码是对我所拥有的内容的高度简化,当从服务调用事件处理程序时,它仍然会导致竞争条件。

    模型

    public class Model
    {
    public int Id { get; set; }
    public string Msg { get; set; }
    }

    我的上下文

    public class MyContext : DbContext
    {
    public MyContext() : base()
    {
    Models = Set<Model>();
    }

    public MyContext(DbContextOptions<MyContext> options) : base(options)
    {
    Models = Set<Model>();
    }

    public DbSet<Model> Models { get; set; }
    }

    模型服务

    public class ModelService
    {
    private readonly MyContext context;
    private event EventHandler? CollectionChangedCallbacks;

    public ModelService(MyContext context)
    {
    this.context = context;
    }

    public void RegisterCollectionChangedCallback(EventHandler callback)
    {
    CollectionChangedCallbacks += callback;
    }

    public void UnregisterCollectionChangedCallback(EventHandler callback)
    {
    CollectionChangedCallbacks -= callback;
    }

    public async Task<Model[]> FindAllAsync()
    {
    return await Task.FromResult(context.Models.ToArray());
    }

    public async Task CreateAsync(Model model)
    {
    context.Models.Add(model);
    await context.SaveChangesAsync();

    // No args necessary; the callbacks know what to do.
    CollectionChangedCallbacks?.Invoke(this, EventArgs.Empty);
    }
    }

    Startup.cs(摘录)

    public void ConfigureServices(IServiceCollection services)
    {
    services.AddRazorPages();
    services.AddServerSideBlazor();

    string connString = Configuration["ConnectionStrings:DefaultConnection"];
    services.AddDbContext<MyContext>(optionsBuilder => optionsBuilder.UseSqlServer(connString), ServiceLifetime.Transient);
    services.AddScoped<ModelService>();
    }

    ParentPage.razor

    @page "/simpleForm"

    @using Data
    @inject ModelService modelService
    @implements IDisposable

    @if (AllModels is null)
    {
    <p>Loading...</p>
    }
    else
    {
    @foreach (var model in AllModels)
    {
    <label>@model.Msg</label>
    }

    <label>Other view</label>
    <ChildComponent></ChildComponent>

    <button @onclick="(async () => await modelService.CreateAsync(new Model()))">Add</button>
    }

    @code {
    private Model[] AllModels { get; set; } = null!;
    public bool ShowForm { get; set; } = true;
    private object disposeLock = new object();
    private bool disposed = false;

    public void Dispose()
    {
    lock (disposeLock)
    {
    disposed = true;
    modelService.UnregisterCollectionChangedCallback(CollectionChangedCallback);
    }
    }

    protected override async Task OnInitializedAsync()
    {
    AllModels = await modelService.FindAllAsync();
    modelService.RegisterCollectionChangedCallback(CollectionChangedCallback);
    }

    private void CollectionChangedCallback(object? sender, EventArgs args)
    {
    // Feels dirty that I can't await this without changing the function signature. Adding async
    // will make it unable to be registered as a callback.
    InvokeAsync(async () =>
    {
    AllModels = await modelService.FindAllAsync();

    // Protect against event-handler-invocation race conditions with disposing.
    lock (disposeLock)
    {
    if (!disposed)
    {
    StateHasChanged();
    }
    }
    });
    }
    }

    ChildComponent.razor
    Copy-paste (for the sake of demonstration) of ParentPage minus the label, ChildComponent, and model-adding button.

    注:我还尝试尝试将一段代码插入组件的 HTML 部分,但这也不起作用,因为我无法使用 await。那里。

    我尝试过的可能是个坏主意(并且仍然无法避免线程冲突):

        @if (AllModels is null)
    {
    <p><em>Loading...</em></p>
    @Load();
    @*
    Won't compile.
    @((async () => await Load())());
    *@
    }
    else
    {
    ...every else
    }

    @code {
    ...Initialization, callbacks, etc.

    // Note: Have to return _something_ or else the @Load() call won't compile.
    private async Task<string> Load()
    {
    ActiveChargeCodes = await chargeCodeService.FindActiveAsync();
    }
    }

    请帮忙。我正在(对我而言)未知领域进行试验。

    最佳答案

    由于我目前的情况与您的情况非常相似,因此让我分享一下我发现的情况。我的问题是“StateHasChanged()”。由于我也在您的代码中看到了该调用,因此以下内容可能会有所帮助:
    我有一个非常简单的回调处理程序:

        case AEDCallbackType.Edit:
    // show a notification in the UI
    await ShowNotification(new NotificationMessage() { Severity = NotificationSeverity.Success, Summary = "Data Saved", Detail = "", Duration = 3000 });
    // reload entity in local context to update UI
    await dataService.ReloadCheckAfterEdit(_currentEntity.Id);
    通知功能这样做:
    async Task ShowNotification(NotificationMessage message)
    {
    notificationService.Notify(message);
    await InvokeAsync(() => { StateHasChanged(); });
    }
    重载功能这样做:
    public async Task ReloadCheckAfterEdit(int id)
    {
    Check entity = context.Checks.Find(id);
    await context.Entry(entity).ReloadAsync();
    }
    问题是 StateHasChanged() 调用。它告诉 UI 重新渲染。 UI 由一个数据网格组件组成。数据网格调用数据服务中的查询,以从数据库中获取数据。
    这发生在调用“ReloadAsync”之前,这是“等待”。一旦 ReloadAsync 实际执行,它就会发生在不同的线程中,从而导致可怕的“在前一个操作完成之前在此上下文上启动了第二个操作”异常。
    我的解决方案是从原来的位置完全删除 StateHasChanged 行,并在其他所有操作完成后调用它一次。没有更多的并发调用者问题。
    祝你好运解决这个问题,我感觉到你的痛苦。

    关于c# - Blazor 组件 : notify of collection-changed event causing thread collisions,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59742779/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com