Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why does calling a method with await inside not block parent method`s execution?

I have read an answer to a similar question https://stackoverflow.com/a/43841624/11478903 but it doesn’t explain the yielding of execution.

I have a thread that consumes events using GetConsumingEnumerable from a BlockingCollection<Event> _eventQueue property.

public async Task HadleEventsBlocking()
{
    foreach (var event in _eventsQueue.GetConsumingEnumerable())
    {
        switch (event)
        {
            case Event.BtnAddClicked:
                HandleBtnAddClickedAsync(_cts.Token);
                break;
            case Event.BtnRemoveClicked:
                HandleBtnRemoveClickedAsync(_cts.Token);
                break;
            case Event.BtnDisableClicked:
                _cts.Cancel();
                break;
            case Event.BtnEnableClicked:
                _cts.Dispose();
                _cts = new CancellationTokenSource();
                break;
        }
        Console.WriteLine("Event loop execution complete.");
    }
}

public async Task HandleBtnAddClickedAsync(CancellationToken token)
{
    try
    {
        await Task.Run(async () =>
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            token.ThrowIfCancellationRequested();
            Console.WriteLine("BtnAddClicked event complete");
        });
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("HandleBtnAddClicked Cancelled");
    }
}
    
public async Task HandleBtnRemoveClickedAsync(CancellationToken token)
{
    try
    {
        await Task.Run(async () =>
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            token.ThrowIfCancellationRequested();
            Console.WriteLine("BtnRemoveClicked event complete");
        });
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("HandleBtnRemoveClicked Cancelled");
    }
}

And this does exactly what I want, the foreach loop executes each Event as fast as possible and does not get blocked. The methods that correspond to each Event also get the convenience of try/catch with the await Task.Run but why does this work? Because if I simply rearrange it won’t work as I want it to.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

public async Task HadleEventsBlocking()
{
    foreach (var event in _eventsQueue.GetConsumingEnumerable())
    {
        switch (event)
        {
            case Event.BtnAddClicked:
                try
                {
                    await Task.Run(async () =>
                    {
                        _cts.Token.ThrowIfCancellationRequested();
                        await Task.Delay(2000);
                        _cts.Token.ThrowIfCancellationRequested();
                        Console.WriteLine("BtnAddClicked event complete");
                    });
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("HandleBtnAddClicked Cancelled");
                }
                break;
            case Event.BtnRemoveClicked:
                try
                {
                    await Task.Run(async () =>
                    {
                        _cts.Token.ThrowIfCancellationRequested();
                        await Task.Delay(2000);
                        _cts.Token.ThrowIfCancellationRequested();
                        Console.WriteLine("BtnRemoveClicked event complete");
                    });
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("HandleBtnRemoveClicked Cancelled");
                }
    
                break;
            case Event.BtnDisableClicked:
                _cts.Cancel();
                break;
            case Event.BtnEnableClicked:
                _cts.Dispose();
                _cts = new CancellationTokenSource();
                break;
        }
        Console.WriteLine("Event loop execution complete.");
    }
}

Now each time an event is executed the foreach loop is blocked by the await inside the try/catch and I understand why, because of the await on the Task.Run.

However I don’t understand why I get desired behavior when I pack it into a method that I don’t await. Is it because the await inside yields execution back to HandleEventsBlocking and it resumes the foreach loop? I’d also appreciate a comment on whether this is good practice, it got me far but I just don’t understand the tool I’m using it and it makes me worried.

>Solution :

However I don’t understand why I get desired behavior when I pack it into a method that I don’t await.

Because it’s not awaited. You can think of await as "asynchronous wait". It pauses the method until the returned task completes. More info on my blog: https://blog.stephencleary.com/2012/02/async-and-await.html

I’d also appreciate a comment on whether this is good practice, it got me far but I just don’t understand the tool I’m using it and it makes me worried.

Absolutely not. Ignoring tasks instead of awaiting them is dangerous: it means any exceptions are silently swallowed and your code cannot know when the processing is complete.

A better approach would be something like TPL Dataflow. Alternatively, you could create multiple consumers for your queue of work.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading