Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update a property asynchronous

Tags:

c#

.net

blazor

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?

like image 225
vernou Avatar asked Nov 18 '18 18:11

vernou


People also ask

Can a property be async?

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.

Is async property allowed in C#?

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.

How to write asynchronous code in C#?

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.

Is await async the same as sync?

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.


2 Answers

You should do the following:

  1. Define an action delegate in your class:

     public event Action OnChange;
    
  2. 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.

  3. 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;
         }
     }
    
like image 77
enet Avatar answered Oct 12 '22 22:10

enet


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( () =>
    {
       ...
like image 22
dani herrera Avatar answered Oct 12 '22 23:10

dani herrera