0

I have a child component in Blazor Server, which displays a table. On click of a row in that table, I have the following handler method and event callback to send the data to the parent component (which wraps the child component in a popup that needs to be toggled when new data comes through).

The object get/set comes from DevExpress and I can't seem to make that method async without throwing an error, yet the event callback requires async.

[Parameter]
public EventCallback<DataObj> OnDataObjSelection { get; set; }
DataObj _selectedDataObject { get; set; }

object SelectedDataObject
    {
        get
        {
            return _selectedDataObject;
        }
        set
        {
            _selectedDataObject = value as DataObj;
            new Task(async () =>
            {
                await OnDataObjSelection.InvokeAsync(_selectedDataObject);
            }).Start();
        }
    }

In the parent component, I have this function triggered on the event callback (which will hide the popup wrapping the child component if it's visible):

 async void DataObjSelectionChanged(DataObj dataObj)
        {
            _currentDataObj = dataObj;
    
            if (_showPopup)
            {
                await InvokeAsync(() =>
                {
                    _showPopup = false;
                    StateHasChanged();
                });
            }
        }

Yet, I'm getting the following error: Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state Blazor Server

Anyone know how to get around this? I thought I had the syntax correct, but apparently not.

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • There's no reason for `InvokeAsync` and dispatchers (which don't exist in this case anyway) since 2012, when `async/await` was introduced. Blazor isn't WPF (which doesn't need dispatchers and InvokeAsync anymore either). There aren't multiple threads in a browser, no dispatcher. There's never any good reason to create cold tasks either. In Blazor Server, your code runs on the server and *asynchronously* updates the browser DOM through SignalR messages – Panagiotis Kanavos Apr 06 '22 at 15:53
  • @PanagiotisKanavos - Blazor's ComponentBase does have an InvokeAsync method and sometimes you really have to use it. Not clear if that is the case here though. – H H Apr 06 '22 at 15:59
  • @Slapping - That quote is not an error but an advice (might belong to an error). Always post full error messages and make clear on which line they are. – H H Apr 06 '22 at 16:02
  • @HenkHolterman if this is an attempt to implement parameter binding, it can be replaced with just 4-5 lines. – Panagiotis Kanavos Apr 06 '22 at 16:03
  • @Slapping: get rid of the `async void` and `new Task()` constructs. – H H Apr 06 '22 at 16:16
  • The core problem is raising the async callback from a sync property setter. Ignoring the Task may be the lesser evil here: `_ = OnDataObjSelection.InvokeAsync(_selectedDataObject);` – H H Apr 06 '22 at 16:18
  • @Panagiotis Kanavos, with all due respect, your comments are baseless and wrong. – enet Apr 06 '22 at 17:19
  • @enet you mean `new Task` is useful? Or that the intent of this code is clear? I'm using Blazor for 2 years and never had to use such code, I always use code like the accepted answer. No dispatchers, no ComponentBase.InvokeAsync. – Panagiotis Kanavos Apr 06 '22 at 17:32
  • @Panagiotis Kanavos, as the code snippet in the question involves component data-binding (one way), the call to InvokeAsync is superfluous, but very often, when a call is made to code external to the Component Model, such as a service, which is executed in a potential "background" thread, you must call StateHasChanged from within InvokeAsync to adjust the synchronization context. `There aren't multiple threads in a browser, no dispatcher.` This is a Blazor Server App, and there are multiple threads to take into consideration, and the code is not executed on the browser, but on the server. – enet Apr 06 '22 at 19:24

1 Answers1

2

As has been alluded to above, you are overcomplicating this.

[Parameter]
public EventCallback<DataObj> OnDataObjSelection { get; set; }
DataObj _selectedDataObject { get; set; }

object SelectedDataObject
    {
        get =>  _selectedDataObject;
        set
        {
            _selectedDataObject = value as DataObj;
            OnDataObjSelection.InvokeAsync(_selectedDataObject);
        }
    }

And:

private void DataObjSelectionChanged(DataObj dataObj)
{
    _currentDataObj = dataObj;
    if (_showPopup)
         StateHasChanged();
}
enet
  • 41,195
  • 5
  • 76
  • 113
MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31