Potential deadlock being caused by this async/await code?

  • A+
Category:Languages

I have the following class:

public abstract class ServiceBusQueueService : IServiceBusQueueService {     private readonly string _sbConnect;      protected ServiceBusQueueService(string sbConnect)     {         _sbConnect = sbConnect;     }      public async Task EnqueueMessage(IntegrationEvent message)     {         var topicClient = new TopicClient(_sbConnect, message.Topic, RetryPolicy.Default);         await topicClient.SendAsync(message.ToServiceBusMessage());     } } 

Which is being used like this:

public ulong CreateBooking() {      // Other code omitted for brevity       ulong bookingId = 12345; // Pretend this id is generated sequentially on each call        _bookingServiceBusQueueService.EnqueueMessage(new BookingCreatedIntegrationEvent       {             BookingId = bookingId       }).GetAwaiter().GetResult();        return bookingId; } 

When the EnqueueMessage method is called from my CreateBooking method the program hangs and does not go any further after hitting the line await topicClient.SendAsync(message.ToServiceBusMessage());

Now the code works and my messages get successfully pushed to the Service bus when I change the call to my EnqueueMessage method as follows:

Task.Run(() => _bookingServiceBusQueueService.EnqueueMessage(new BookingCreatedIntegrationEvent         {             BookingId = bookingId         })).Wait(); 

I am not very familiar with using async/await, after a little bit of research it sounds like it was causing a deadlock, is this correct? Why does changing the method call to be Task.Run(() => SomeAsyncMethod()).Wait(); cause it to stop hanging and work as intended?

 


Suppose I gave you the following workflow:

  1. write a note that says "mow the lawn" and put it on the fridge.
  2. do nothing whatsoever until the task mentioned in the note is complete.
  3. Make a sandwich
  4. Do the tasks written on notes on the fridge.

If you followed that workflow, you would get to step (2), and then do nothing forever, because you're waiting for step (1)'s task to complete, which is not started until step (4).

You're encoding effectively that same workflow in software, so it should be no wonder that you're waiting forever.

So why does adding a Run "fix" it? Here's another workflow:

  1. write a note that says "mow the lawn", hire a worker, and give that note to the worker
  2. do absolutely nothing until the task mentioned in the note is complete
  3. Make a sandwich

Now you do not wait forever. You wait for the worker to mow your lawn, which is merely inefficient and wasteful. You could be doing other work while you're waiting, like making that sandwich. Or you could mow the lawn yourself and not take on the expense of hiring a worker.

That's why you never, ever synchronously wait on an asynchronous operation. There are only two possibilities: if you are doing the asynchronous operation in the future, you wait forever, which is obviously broken. If you are not, then you're wasting time sleeping when you could be doing work, which is obviously wasteful.

Use asynchrony as it was designed to be used: asynchronously.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: