async-awaitsynchronizationblazor-server-side

Two independent Blazor tasks, pause one until the other completes


I need to complete one Task before another Task completes. Both are called by Blazor, to distinct components, and therefore I have no knowledge or control over which is called first and therefore the must complete task could be complete before the delay until task has even been called.

Here's the specifics. I have a form that uses <DxFormLayoutTabPages> that provides a tabbed set of pages, each page has components in it. This component has an ActiveTabIndexChanged event.

In one of the pages I have a <DxRichEdit> component. It has a LostFocus event.

What happens is when the user switches from the tab with the RichEdit to another tab the RichEdit LostFocus event is called and the TabPages ActiveTabIndexChanged is called. When the ActiveTabIndexChanged completes the RichEdit is disposed, regradless of the state of the LostFocus event.

In the LostFocus event I call await RichEditDesc.SaveDocumentAsync(); and that, before returning, calls DocumentContentChanged event passing the editor's document. In that call I save off the contents for when the user later clicks [Save].

Now if LostFocus completes before ActiveTabIndexChanged completes, life is great and I have the document contents saved off. But if ActiveTabIndexChanged completes before the call to SaveDocumentAsync completes, the RichEdit component is disposed, the call to DocumentContentChanged does not happen, and I've lost the document content.

The easy solution is:

private async Task OnActiveTabIndexChanged(int idx)
{
    if (ActiveIndex == 5)
        await Task.Delay(1000);
    ActiveIndex = idx;
}

But I dislike this because first, makes for a UI that freezes for a second and second, what about the rare situation where 1 second is not long enough.

The problem I am struggling with is, based on logging I added, I have seen both:

  1. ActiveTabIndexChanged
  2. LostFocus

and

  1. LostFocus
  2. ActiveTabIndexChanged

So setting an int, signal, whatever - I don't see where/how to set that, to then await its completion in ActiveTabIndexChanged. I can't set it up when the form is created because people can go to the RichEdit tab, then another tab, then back to the RichEdit tab.

This is not a Blazor question, unless it has something I'm not aware of to handle situations like this. This is a question of synchronizing two tasks where I have no control over what order the events are called in, nothing to tell me they're about to be called, and this can all happen multiple times.

And to keep this simple, I just listed the above. But the same issue occurs if the user clicks [Save] when on the tab with the RichEdit. In that case there's an AppointmentFormClosing event. Same issue with LoseFocus. So the LoseFocus part of this has to work with either of the two other events being called.

Very simple razor:

<DxScheduler AppointmentFormClosing="AppointmentFormClosing">
  <AppointmentFormLayout>
    <DxFormLayoutTabPages ActiveTabIndex="@ActiveIndex" ActiveTabIndexChanged="OnActiveTabIndexChanged">
      <DxFormLayoutTabPage Caption="Details">
        <DxRichEdit LostFocus="OnRichEditDescLostFocus" ModifiedChanged="OnDocumentDescModified" DocumentContentChanged="OnDocumentDescChanged">
      </DxFormLayoutTabPage>
    </DxFormLayoutTabPages>
  </AppointmentFormLayout>
</<DxScheduler>

Solution

  • I came up with a simple/clean solution. Key is that in this case it does not matter if I call await RichEditDesc.SaveDocumentAsync(); multiple times. The DxRichEdit ignores the call after the first time because the content is no longer modified. But even if it processed it multiple times, all that would happen is that I save of the byte array again.

    So in both ActiveTabIndexChanged and AppointmentFormClosing (as well as LostFocus), I call SaveDocumentAsync(). It will get called twice. That works fine.

    Not need for any complex use of a shared int, semaphore, etc.

    Update: Per MrC's answer & comments below, I agree 100% with him that requiring code to be called twice is almost always a very bad sign. But this does not require the code be called twice.

    In this case it will always call one of the two events. It will often call both of the two events. If there is no downside to the save being called twice, then putting it in both events works.