更新一个 属性 异步
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 的解决方案吗?
您应该执行以下操作:
在您的 class:
中定义一个动作委托
public event Action OnChange;
在此非常class定义一个方法NotifyStateChanged()
如下:
private void NotifyStateChanged() => OnChange?.Invoke();
此方法触发 OnChange
事件。您应该在完成任何任务后从您的逻辑中调用此方法。
在你的 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( () =>
{
...
当我的 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 的解决方案吗?
您应该执行以下操作:
在您的 class:
中定义一个动作委托public event Action OnChange;
在此非常class定义一个方法
NotifyStateChanged()
如下:private void NotifyStateChanged() => OnChange?.Invoke();
此方法触发
OnChange
事件。您应该在完成任何任务后从您的逻辑中调用此方法。在你的 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( () =>
{
...