更新一个 属性 异步

Update a property asynchronous

当我的 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;
        }
    }
}

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

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

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

但是我不能在 TodoItem 中调用这个方法(我不想让 TodoItem 知道 Blazor 组件)。

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

您应该执行以下操作:

  1. 在您的 class:

    中定义一个动作委托
     public event Action OnChange;
    
  2. 在此非常class定义一个方法NotifyStateChanged()如下:

     private void NotifyStateChanged() => OnChange?.Invoke();
    

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

  3. 在你的 todo 组件中,将 StateHasChanged 方法添加到你的 TodoItem class 中使用的事件委托中,因此:

     @functions
     {
         protected override void OnInit()
         {
             state.OnChange += StateHasChanged;
         }
     }
    

简单的答案是 "just fire StateHasChanged(); after modify your var":

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

因为你的方法是异步的,重写为:

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

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

我在这里写下修改后的 Blazor 计数器示例:

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

已编辑

不再支持从线程调用 StateHasChanged。只需更改:

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

    Invoke( () =>
    {
       ...