Search code examples
c#.netasynchronousasync-await

Async method call is blocking main thread


I have an async method like this:

public async Task SpecifyAsync()
{
    CancellationTokenSource source = new();
    Task<long> checkTask = BeginCheckingAsync(source.Token);
    SendMessage();
    await сheckTask;
}

Async method is called at its second line. As far as I understand, this shouldn't block main execution thread. However, third line is unreachable until checkTask is finished. Can you, please, describe me, what am I doing wrong?

UPD. BeginCheckingAsync code is something like this:

private async Task<long> BeginCheckingAsync(CancellationToken cancellationToken)
{
    while (true)
    {
        var messages = _apiClient.GetNewMessages();

        foreach (var message in messages)
            if (ValidateMessage(message))
                return await Task.FromResult(Parse(message));

        cancellationToken.ThrowIfCancellationRequested();
    }
}

Solution

  • There are two issues with your method:

    1. It is not truly async one (hence it will not return to the caller until completed). Awaiting a completed task (i.e. await Task.FromResult) is not an asynchronous operation, so it will not result in control passed to the caller.
    2. Even if awaiting a completed task would be an asynchronous operation, everything up to the first await in the BeginCheckingBalanceAsync will be executed synchronously, i.e. if _apiClient.GetNewMessages is a long-running operation or ValidateMessage will return false for some iterations, everything before awaiting first "truly async" call would be executed synchronously

    Depending on actual implementations you can use Task.Run in appropriate parts of the method. For example:

    private async Task<long> BeginCheckingBalanceAsync(CancellationToken cancellationToken)
    {
        while (true)
        {
            var messages = await Task.Run(() => _apiClient.GetNewMessages());
    
            foreach (var message in messages)
                if (ValidateMessage(message))
                    return await Task.FromResult(Parse(message));
    
            cancellationToken.ThrowIfCancellationRequested();
        }
    }
    

    But according to the answer for Should I expose asynchronous wrappers for synchronous methods?:

    My short answer to such a question is “no.”

    Just use Task.Run on the caller side, do not make BeginCheckingAsync async itself.

    Some potentially useful links: