Question

This may have been answered somewhere on the web but I've looked at lots of the examples and I still can't figure it out.

I have a sync based service layer boundary (e.g. "ProcessOrder" method). Within this method it performs various functions, a couple of which have async overloads and it makes sense to use these as they wrap I/O bound functions.

However, my confusion is two-fold. From what I've read:

  • async code should be "async all the way down"
  • async marked methods should not return void - this is an edge case for event handlers and
    doesn't handle bubbling up exceptions

So, with example code such as below (NB: _publisher is wrapper for Azure QueueClient that has Async Send methods):

public void ProcessOrder()
{
    // other stuff...

    _publisher.PublishAsync(new ItemOrderedEvent()
    {
        MessageId = Guid.NewGuid(),
        IsDurable = true,
    }).Wait();
}

Doesn't seem to fit the "async all the way down" rule, and there are warnings about deadlocks etc. on the web due to thread pool exhaustion at high loads about this pattern, but code such as:

public async void ProcessOrder()
{
    // other stuff...

   await  _publisher.PublishAsync(new ItemOrderedEvent()
    {
        MessageId = Guid.NewGuid(),
        IsDurable = true,
    });
}

Breaks the "async marked methods should not return void" rule and will not handle exceptions as, from my understanding, there is no Task context to bubble them back from the async method. NB: This is a service boundary - the calling code may be UI, in response to an MSMQ message, a web service call etc. so I do not want "implementation" of the service call to leak higher up. In the current context, IMO, it makes sense for this to be a sync call only.

The publisher method is defined as:

public async Task PublishAsync(IMessageBusMessage message)
{
    // do azure stuff
    var _queue = QueueClient.CreateFromConnectionString(connectionString, "ItemOrdered");
    await _queue.SendAsync(message);
}

So my question is... how the heck do you make use of legitimate I/O bound async methods within a sync based architecture? The typical answer seems to be "you can't" (as suggested by this Calling async methods from a synchronous context) but then I must be missing something as it seems perfectly reasonable to want to offload I/O bound work and let the current thread do some other work, in contexts that are not UI based.

Was it helpful?

Solution 2

it seems perfectly reasonable to want to offload I/O bound work and let the current thread do some other work, in contexts that are not UI based.

If this is a non-UI thread, and you do have some other work to do while your IO-bound operation is pending, then nothing prevents your from doing so inside your synchronous method:

public void ProcessOrder()
{
    // other stuff...

    // initiate the IO-bound operation
    var task = _publisher.PublishAsync(new ItemOrderedEvent() {});
    {
        MessageId = Guid.NewGuid(),
        IsDurable = true,
    }); // do not call .Wait() here

    // do your work
    for (var i = i; i < 100; i++)
        DoWorkItem(i);

    // work is done, wait for the result of the IO-bound operation
    task.Wait(); 
}

This might make sense if you cannot afford re-factoring all of your code to use "async all the way down" philosophy (which may be time-consuming, but almost always possible).

OTHER TIPS

NB: This is a service boundary - the calling code may be UI, in response to an MSMQ message, a web service call etc. so I do not want "implementation" of the service call to leak higher up. In the current context, IMO, it makes sense for this to be a sync call only.

Actually, you've got that backwards. :)

Consider this method declaration:

public void ProcessOrder();

That is most definitely synchronous. If it does anything asynchronous, it'll be blocking, so it'll act like it's synchronous. The only way it could be interpreted as asynchronous is if it took some kind of callback or was paired with an event or something like that, and in that case it would unambiguously be asynchronous.

Now consider this method declaration:

public Task ProcessOrderAsync();

That is most likely asynchronous. But it could possibly be synchronous, if it returns a task that is already completed. Await has a "fast path" shortcut that it uses to handle this scenario, and actually treat it as synchronous. Caching is one example where this is used in practice; a cache hit is synchronous.

So, if you want to abstract away the asynchrony of your implementation, then you should use asynchronous method declarations.

Note: a Task-returning method is a (possibly-)asynchronous declaration, whereas async is an implementation detail.

That said, there are a variety of ways to "wrap" asynchronous code into a synchronous API, but every one of them has drawbacks and none of them works in every scenario. It's best to keep your APIs telling the truth: if any implementation may be asynchronous, then the method declaration should be asynchronous.

how the heck do you make use of legitimate I/O bound async methods within a sync based architecture?

You can't have your cake and eat it too. You either go async, and then you have to "go async all the way", or you go sync and then you block.

You can, and this is bad practice, spin another thread and do the blocking IO there, but that will be wasting a thread just to block so you can do more work in the meanwhile.

public void ProcessOrder()
{
  // other stuff...

  Task.Run(async () => await _publisher.PublishAsync(new ItemOrderedEvent()
  {
    MessageId = Guid.NewGuid(),
    IsDurable = true,
  }).ContinueWith((Task task) => //do continuation if you need);

}

But spinning off such a thread loose is terribly bad practice.

I would either go async all the way if possible, or use a blocking sync call (call Result Or Wait as you did in your original post)

public async Task ProcessOrder()
{
  // other stuff...

 await _publisher.PublishAsync(new ItemOrderedEvent()
 {
    MessageId = Guid.NewGuid(),
    IsDurable = true,
 });
}  
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top