异步更新属性

Orw*_*wel 5 .net c# blazor

当我的 ViewModel 的属性更新时,其他属性会异步更新。

Todo.cshtml:

@page "/todo"

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" bind="@todo.IsDone" />
            <input bind="@todo.Title" />
        </li>
    }
</ul>

<input placeholder="Something todo" bind="@newTodo"/>
<button onclick="@AddTodo">Add todo</button>

@functions {
    IList<TodoItem> todos = new List<TodoItem>();


    string newTodo;
    void AddTodo()
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

TodoItem.cs:

public class TodoItem
{
    private bool _isDone;

    public string Title { get; set; }
    public bool IsDone
    {
        get => _isDone;
        set
        {
            _isDone = value;
            Task.Run(() =>
            {
                //Simulate work
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
                //Update property
                Title = Title + " - Done";
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在同步(没有 Task.Run)中,这个工作正常,但在异步中,UI 不会更新。

我需要解释要更新的 UI StateHasChanged()https : //github.com/aspnet/Blazor/issues/1413

但是我不能在 TodoItem 中调用这个方法(而且我不希望 TodoItem 知道 Blazor 组件)。

您有更新 UI 的解决方案吗?

Isa*_*aac 6

您应该执行以下操作:

  1. 在你的类中定义一个动作委托:

     public event Action OnChange;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在这个类中定义一个方法NotifyStateChanged()如下:

     private void NotifyStateChanged() => OnChange?.Invoke();
    
    Run Code Online (Sandbox Code Playgroud)

    该方法触发OnChange事件。在完成它所做的任何任务后,您应该从逻辑中调用此方法。

  3. 在您的待办事项组件中,将StateHasChanged方法添加到您的TodoItem类中使用的事件委托中:

     @functions
     {
         protected override void OnInit()
         {
             state.OnChange += StateHasChanged;
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)


dan*_*era 5

简单的答案是“修改你的变量后就触发StateHasChanged();” :

        Task.Run(() =>
        {
            //Simulate work
            System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
            //Update property
            Title = Title + " - Done";
            StateHasChanged();
        });
Run Code Online (Sandbox Code Playgroud)

因为您的方法是异步的,所以重写为:

        Task.Run(async () =>  //<--here async
        {
            //Simulate async work
            Task.Run( async () => {
                await Task.Run( () => {} ); //<--- await
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
            });
            //Update property
            Title = Title + " - Done";
            StateHasChanged();
        });
Run Code Online (Sandbox Code Playgroud)

为了避免反模式并编写干净的代码,您的 ModelView 可能有一个公共事件来让 UI 知道它已更改,只需将 UI 上的此事件连接到StateHasChanged();.

我在这里编写了经过修改的 Blazor Counter 示例来执行此操作:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" onclick="@IncrementCount">
    Click me @s <!-- here var -->
</button>

@functions {
    int currentCount = 0;

    string s = "";

    void IncrementCount()
    {
        currentCount++;
        Task.Run(() =>
            {
                //Simulate work
                Task.Run( async () => {
                    await Task.Run( () => {} );
                    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));}
                        );
                //Update property
                s = s + " - Done";
                StateHasChanged();
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

已编辑

StateHasChanged不再支持来自线程的调用。只是改变:

    Task.Run(() =>
    {
        //Simulate work
        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
        //Update property
        Title = Title + " - Done";
        StateHasChanged();
    });
Run Code Online (Sandbox Code Playgroud)

经过

    Invoke( () =>
    {
       ...
Run Code Online (Sandbox Code Playgroud)