Измененное состояние не выполняется при асинхронном вызове серверного приложения Blazor

#blazor #blazor-server-side

Вопрос:

Когда пользователь нажимает на определенную кнопку, мне нужно показать анимацию внутри кнопки и отключить ее ( для выполнения метода требуется несколько секунд). Это мой html:

  <div class="row">
            <div class="col-lg-4">
                @if (IsDownloading)
                {
                    <button class="btn btn-primary float-right search disabled" disabled><span class='fa-left fas fa-sync-alt spinning'></span>Downloading...</button>
                }
                else
                {
                    <button id="REC_PDF" class="btn btn-primary" @onclick="@(()=>getPDF())">Download Labels</button>
                }
                
            </div>
        </div>
 

и это мой код:

 protected bool IsDownloading { get; set; }

public async Task getPDF()
{
    IsDownloading = true;
    StateHasChanged();

    await generate_pdf(labels);

    IsDownloading = false;
    StateHasChanged();
}

private async Task generate_pdf(List<Tuple<string, string, string, string>> filtered_label)
{
    loading = true;
    string PDF_PATH = "./PDF/"   CurrentValue;
    foreach (Tuple<string,string,string,string> label in filtered_label)
    {

        if (!Directory.Exists(PDF_PATH))
        {
            Directory.CreateDirectory(PDF_PATH);
        }
.......
 

Код работает, но рендеринг страницы, вызванной StateHasChanged(); выполняется 2 раза, когда метод getPDF ( и generate_PDF последовательно) полностью выполнен.

Мне нужен рендеринг страницы при первом вызове, чтобы показать анимацию загрузки css.

( как предлагалось в другом посте, я использовал InvokeAsync(() => StateHasChanged()); но не работает)

Спасибо.

Комментарии:

1. @onclick="@(()=>getPDF())" <- Почему? Удалив всю информацию о задаче и, таким образом, ничего не ожидая. @onclick="getPDF" это правильный путь… но не причина вашей проблемы.

Ответ №1:

Событие щелчка обрабатывается следующим образом в Blazor:

 var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
    await task;
    StateHasChanged();
}
 

Примечание StateHasChanged на самом деле не выполняет повторную визуализацию компонента, он просто помещает RenderFragment делегата в очередь визуализации.

Весь код выполняется в потоке «Пользовательский интерфейс», поэтому событие рендеринга, поставленное в очередь при вызове StateHasChanged , выполняется только либо:

  • в этот момент метод события дает контроль
  • если выход не произойдет (потому что в блоке задач есть только код синхронизации) в конце.

Я не вижу никакого кода, который дает результат в вашем generate_pdf .

Попробуйте сделать следующее:

 public async Task getPDF()
{
    IsDownloading = true;
    await Task.Yield();

    await generate_pdf(labels);

    IsDownloading = false;
}
 

Примечание: Directory.CreateDirectory не является асинхронным методом. Имеет только FileStream асинхронные методы.

Комментарии:

1. Ладно, я понял. Это реализация в рабочем приложении, поэтому я пытался реализовать эту функцию без переписывания структуры метода. Большое спасибо.

2. Спасибо вам за объяснение. После 2 дней перепробования множества различных подходов, ВЫХОД был тем, что мне было нужно.

Ответ №2:

Проблема в том, что generate_pdf() это не асинхронно. Добавления async недостаточно. Вам нужно что-то асинхронное, чтобы включить рендеринг.

Поскольку это проблема пользовательского интерфейса, я предлагаю решить ее на верхнем уровне:

 public async Task getPDF()
{
    IsDownloading = true;
    //StateHasChanged();  // not needed at start
    await task.Delay(1);  // allow the UI to update    

    try
    {
      await generate_pdf(labels);     
    }
    finally
    {
      IsDownloading = false;         
    }
    //StateHasChanged();  // not needed at the end
}
 

Когда функция generate_pdf() сама по себе ничего не делает await , вы также можете сделать ее обычным методом void.

Ответ №3:

Попробуйте эти модификации:

 public async Task getPDF()
{
    if (IsDownloading) return;

    IsDownloading = true;
    StateHasChanged();

    await generate_pdf(labels);
}
 

затем в конце generate_pdf (лучше внутри блока try/finally) добавьте:

     IsDownloading = false;
    StateHasChanged();