簡體   English   中英

為什么在 Blazor wasm 中使用 await Task.Delay(1)?

[英]Why use await Task.Delay(1) in Blazor wasm?

許多 SO 答案使用await Task.Delay(1)來解決 Blazor (wasm) 中的各種異步渲染問題。 我什至在我自己的代碼中發現了很多地方這樣做“讓它工作”。

然而,它總是作為事實陳述,沒有徹底的解釋,而且我在文檔中也找不到這種技術。

一些問題:

  • 為什么使用await Task.Delay(1) - 我什么時候使用這種技術,用例是什么?
  • 文檔不討論這個(我能找到); 是因為它是黑客攻擊,還是處理用例的合法方式?
  • Task.Delay(1)Task.Yield()之間有什么區別嗎?

為什么使用 await Task.Delay(1) - 我什么時候使用這種技術,用例是什么?

它使 UI 有機會在代碼中間更新和重繪。

文檔不討論這個(我能找到); 是因為它是黑客攻擊,還是處理用例的合法方式?

這是一個黑客。 但在 Blazor/WASM 的特定情況下(沒有其他 Blazor 或 .NET 運行時),沒有很多其他選項。 如果可能的話,我建議拆分您的邏輯,這樣您的應用程序就不會同時做這么多事情; 但有時這是不可能的(或不容易的)。

Task.Delay(1) 和 Task.Yield() 有什么區別?

取決於瀏覽器的詳細信息,是的。

在 Windows UI 應用程序上, Task.Yield對此不起作用,因為 UI 消息循環是一個優先級隊列,“運行此代碼”是最高優先級。 因此(同樣,對於 Windows UI 應用程序),這會將方法的 rest 排隊,然后返回到消息循環,然后繼續執行代碼而不是刷新 UI(這是較低優先級的消息)。

對於 Blazor/WASM, Task.Yield是否有效取決於其(隱式)消息循環的瀏覽器實現。 如果它具有類似的優先級隊列,那么您最終會遇到與 Windows UI 相同的問題,其中Task.Yield確實屈服於消息循環但不會耗盡它。

在所有平台上, Task.Delay(1)實際上都會排隊一個計時器回調,這通常足以在代碼繼續運行之前處理一些 UI 更新。

  • 為什么要使用 await Task.Delay(1)

在事件處理程序中顯示中間結果

  • 文檔不討論這個

通常不需要。 但也沒有人反對使用它。 我想出了在解決這樣的問題時如何使用它 我收到了一些負面反饋,請參閱這些帖子下的評論。

  • Task.Delay(1) 和 Task.Yield() 有什么區別?

是的,Task.Yield() 看起來更明智,但我發現它並不總是有效。 請在此處查看 Stephen Cleary 的回答。

下面是我和Henk Holterman回答的問題的鏈接,他在回答中使用await Task.Delay(1);

運行代碼,看看區別,例如,使用await Task.Delay(1); 導致重新渲染組件兩次,等等。

就是使用await Task.Delay(1); 必要的? 絕對不。 這是一種不好的做法,不僅會導致組件的第二次重新渲染,而且可能會導致復雜代碼出現微妙的問題。 Blazor 提供了一個生命周期方法列表,您可以捕獲並使用這些方法來提供所需的解決方案。 請不要黑客攻擊。 從長遠來看,這可能會非常昂貴。 創建優雅的代碼,而不是黑客...

更新

下面的代碼片段描述了一個使用Task.Delay,展示了一個帶有標題為“保存”的按鈕元素的頁面,要求是在用戶之后立即將標題的文本更改為“正在保存...”在數據存儲中保存員工記錄期間單擊按鈕。 如果您知道Task.Delay,請告訴我。

Index.razor

@page "/"

<div>
    <button class="btn btn-primary" 
                              @onclick="Save">@caption</button>
</div>


@code 
{
    
    private string caption = "Save";

    private async Task SaveEmployee()
    {
        // Simulate saving an employee's record in database...
        // I use System.Threading.Thread.Sleep instead of a loop 
        // with millions of iterations.
        System.Threading.Thread.Sleep(3000);
        // Retruns completed task
        await Task.CompletedTask;
    }

    private async Task Save()
    {
        caption = "Saving...";
        // Renders here automatically after calling Task.Delay()
            
        await Task.Delay(1000);

        await SaveEmployee();

        caption = "Save";
        // Renders here automatically when SaveEmployee() 
        //complete
    }
}

另一方面,下面的代碼片段演示了如何不使用Task.Delay,並提供了一個優雅的解決方案,這當然不是 hack,它的額外優點是它會導致組件的單一渲染Task.Delay涉及第二次渲染,請注意...

注意:下面的代碼是對這個問題的回答

組件.razor

<div @ref="ReferenceToDiv" id="select-@Id" style="background-color: red; width:300px; height: 300px">

 </div>

@code
{
    ElementReference ReferenceToDiv;
    // As you can see, you should call the "adjustPosition" method from the 
    // `OnAfterRenderAsync` method to ensure that the div element has been 
    // rendered. DO Not Re-render In Vain. That is, do not use
    // await Task.Delay(1); to re-render your component

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (opened)
        {
          await jsModule.InvokeVoidAsync("adjustPosition", ReferenceToDiv);                
        } 
    }

    public void OnClick()
    {
        opened = !opened;
       
    }
}

測試.js

export function adjustPosition(element) {
    // Should return 300px
    console.log($(element.style.width);   
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM