2

I think I have a general misunderstanding of the way the async/await pair works. I'm using an EasyNetQ method (an interface for RabbitMQ in C#) and I'm trying to call the following method I created:

public Task<U> RequestDirectReply<T, U>(T request, int timeout) where T : class where U : class
{
    using (var bus = RabbitHutch.CreateBus($"virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName"))
    {
        return bus.RequestAsync<T, U>(request);
    }
}

Now the way that I understand this, I should be able to call this method, get a Task from RequestAsync, then do a bunch of stuff and then await that Task once I'm done with that stuff. Something like this:

Task<Reply> task = RequestDirectReply<Request, Reply>(msg, 10);

for (int i = 0; i < 1000000000; ++i)
{
    // Hi, I'm counting to a billion
}

var reply = await task;

However, the program blocks on the call to RequestAsync for the timeout duration rather than on the await. Then the await immediately throws a timeout exception.

To see if I was misunderstanding, I tried the following as well:

public async Task<U> RequestDirectReply<T, U>(T request, int timeout) where T : class where U : class
{
    using (var bus = RabbitHutch.CreateBus($"virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName"))
    {
        return await bus.RequestAsync<T, U>(request);
    }
}

Same thing. It blocks on the RequestAsync. How is that different than a regular blocking synchronous call?

C. Williamson
  • 319
  • 3
  • 13
  • 2
    It could be an issue in `RequestAsync`. For the sake of simplicity, let us assume there is a Thread.Sleep(10000) in there before itself makes an async call. In that case your call to request will be blocking for 10s. So you will have to invest further down the chain. Maybe it is trying to open a connection synchronously and that fails with a timeout. – Peter Bons Jun 09 '17 at 17:38
  • 1
    Your final code example is better. In the first example, it's quite possible to dispose before completion. – spender Jun 09 '17 at 17:40
  • The answer from Alexei is right. But when you encounter this situation with some API, you can *force* the API off your thread by adding `await Task.Yield();` before `return await bus.RequestAsync`. – Brandon Jun 09 '17 at 18:02

2 Answers2

7

async does not guarantee that code actually will run asynchronously or will not block the calling thread. Ideally it should immediately start operation and return back to caller as you expect, but sometimes (even in existing method of .Net Framework) steps are not completely async.

Sample:

async Task<int> MyAsync()
{
     Thread.Sleep(1000); // (1) sync wait on calling thread
     await Task.Delay(1000); // (2) async wait off calling thread
     Thread.Sleep(1000); // (3) sync wait likely on original thread
}
  1. That Sleep always block calling thread. Task to await is returned after first await call in async method. This is demonstration of the case you are likely observing.
  2. asynchronous waiting, no thread used for the method at this point. Will come back to a thread depending on SynchronizationContext. In most cases - original UI/request thread.
  3. that Sleep need to block some thread. Depending whether SynchronizationContext setup to return to original thread or not wait may happen on the thread that started the call.
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
3

Assuming you're using async/await patterns the way you should, problems like this are usually the fault of the third-party code you use.

The RequestAsync() call you're making gets delegated to the Rpc.Request(...) method code here, which does quite a bit of work before returning a Task. My best guess is that some of that work it does involving Timers ends up blocking, which means that your call to the method itself blocks as well.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Ok, after looking through the code, this seems to be the problem. It turns out that RequestAsync blocks for the timeout period. If I set the timeout to 60, it blocks for a minute. If I set it to 1, it blocks for a second. Kind of defeats the whole point of it being asynchronous. Guess I'll use Rx extentions for my timeout or something... – C. Williamson Jun 09 '17 at 18:19
  • 4
    @C.Williamson: I'd recommend sticking an issue on your library's GitHub page, too. The authors probably don't realize their Async code is blocking. – StriplingWarrior Jun 09 '17 at 18:21