Search code examples
c#async-awaitblazor-server-sidedevexpress-blazor

Task.WaitAll() is never completing


I'm guessing I've got this a little wrong.

I am initializing two DxRichText controls and I think I have something wrong in how I have this set up. But I don't see what. I am trying to have this loading occur in parallel with other initialization.

I have the following methods:

private Task InitializeRichText(DxRichEdit editor, Organization? org,
    RichText? src, RichTextModel model)
{

    if (org == null || src == null)
        return editor.NewDocumentAsync().AsTask();

    model.RichText = src;
    model.BlobUrl = BlobService.GetBlobUrl(src.OpenXmlFilename);
    return LoadUrlAsync(editor, model.BlobUrl);
}

private async Task LoadUrlAsync(DxRichEdit editor, string url)
{
    using (var httpClient = new HttpClient())
    {
        using (var response = await httpClient.GetAsync(url))
        {
            if (response.IsSuccessStatusCode)
            {
                await using (var stream = await response.Content.ReadAsStreamAsync())
                {
                    await editor.LoadDocumentAsync(stream, DocumentFormat.OpenXml);
                }
            }
        }
    }
}

Important item: NewDocumentAsync() and LoadDocumentAsync() return a ValueTask (not a Task).

In my OnInitializedAsync() I have the following (with lots of other code in between):

var listRichTasks = new List<Task>();

await Task.Yield();
listRichTasks.Add(InitializeRichText(RichEditDesc, _organization,
                        _organization?.Description, Model.Description));
listRichTasks.Add(InitializeRichText(RichEditNews, _organization,
                        _organization?.News, Model.News));

// this call is never returning
Task.WaitAll(listRichTasks.ToArray());

What do I have wrong?


Solution

  • Per Microsoft's documentation (thanks @JohanP), you shouldn't call blocking methods in components. One of those specifically mentioned is WaitAll.

    As I understand it, such calls can essentially lead to a deadlock where you're waiting (blocking) for a task to complete, and the task is waiting for the context you're currently blocking to free up so that it can return its results. Essentially they end up waiting for each other forever.

    There's another method, WhenAll, which is an async method that roughly does the same thing (it also returns results), not a sync method like WaitAll.

    Thus we can change your code to this:

    var listRichTasks = new List<Task>();
    
    await Task.Yield();
    listRichTasks.Add(InitializeRichText(RichEditDesc, _organization,
                            _organization?.Description, Model.Description));
    listRichTasks.Add(InitializeRichText(RichEditNews, _organization,
                            _organization?.News, Model.News));
    
    // this call is never returning
    await Task.WhenAll(listRichTasks.ToArray());
    

    And this should get rid of the deadlock you seem to be experiencing.

    For more on async, I recommend Stephen Cleary's blog. A pertinent article was provided in the comments: Don't Block on Async Code.