When a property of my ViewModel is updated, other property is updated asynchronous.
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";
});
}
}
}
In synchronous (without Task.Run) this work fine, but in asynchronous the UI isn't updated.
I need explain the UI to update with StateHasChanged()
:
https://github.com/aspnet/Blazor/issues/1413
But I can't call this method in TodoItem (and I don't want TodoItem know Blazor component).
Have you a solution to update the UI?
Just as Swift's functions can be asynchronous, computed properties can also be asynchronous: attempting to access them must also use await or similar, and may also need throws if errors can be thrown when computing the property.
There is no technical reason that async properties are not allowed in C#. It was a purposeful design decision, because "asynchronous properties" is an oxymoron. Properties should return current values; they should not be kicking off background operations.
The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.
No it's not the same. Your async code block is waiting for the await call to return to continue, however the rest of your application isn't waiting and can still continue like normal.
You should do the following:
Define an action delegate in your class:
public event Action OnChange;
In this very class define a method NotifyStateChanged()
as follows:
private void NotifyStateChanged() => OnChange?.Invoke();
This method triggers the OnChange
event. You should call this method from your logic after fulfilling whatever task it does.
In your todo Component, add the StateHasChanged
method to the event delegate used in your TodoItem
class thus:
@functions
{
protected override void OnInit()
{
state.OnChange += StateHasChanged;
}
}
Easy answer is "just fire StateHasChanged();
after modify your var":
Task.Run(() =>
{
//Simulate work
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
//Update property
Title = Title + " - Done";
StateHasChanged();
});
Because your method is async, rewrite as:
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();
});
To avoid anti-pattern and write clean code, your ModelView may have a public event to let know UI it has changed, just connect this event on UI to StateHasChanged();
.
I write here the Blazor Counter sample modified to do this:
@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();
});
}
}
}
Edited
Call StateHasChanged
from a thread is not more supported. Just change:
Task.Run(() =>
{
//Simulate work
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
//Update property
Title = Title + " - Done";
StateHasChanged();
});
By
Invoke( () =>
{
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With