13

I've done lots of development in C#/.Net, and the asynchronous story has always been there from day one (admittedly the API's have changed significantly over the years from begin/end to events, to Task<T> with async/await). For the last year or so I've been developing with Node.js which does all I/O asynchronously and uses a single threaded event loop model. Recently I was working on a project where we were using Ruby, and for one part of the application, I felt that it made sense to make a whole bunch of web requests asynchronously, and was surprised to find that the asynchronous story in Ruby is vastly different. The only way to do any asynchronous I/O is by using the EventMachine.

My question comes down to this: Why is it that in .Net (and from what I can tell this is true for Java/JVM as well) there's no need for an event loop, and I can fire off an asynchronous request at any time, yet in languages like Ruby/Python, I need to resort to eventmachine/twisted respectively? I feel like there's some fundamental thing about how asynchronous I/O works that I'm not understanding.

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
BFree
  • 102,548
  • 21
  • 159
  • 201

2 Answers2

12

My question comes down to this: Why is it that in .Net (and from what I can tell this is true for Java/JVM as well) there's no need for an event loop, and I can fire off an asynchronous request at any time, yet in languages like Ruby/Python, I need to resort to eventmachine/twisted respectively?

I think that's because Ruby/Python (and seamlessly, Node.js as well) want to make a developer's life easier by imposing single-threaded model for the application's core loop. With event machine, the completion callbacks of the async I/O routines are serialized and queued to be executed on the same thread, so the developer doesn't have to worry about thread safety.

I can't speak for Java, but in .NET we have control over this with synchronization context. Check Stephen Cleary's "It's All About the SynchronizationContext". It's quite easy to replicate the concept of event machine in .NET (in fact, that's automatically done for UI applications). A custom implementation of the serializing synchronization context might look like AsyncPump from Stephen Toub's "Await, SynchronizationContext, and Console Apps". IMO, this would be a direct match to Ruby's event machine.

Adam Simon
  • 2,762
  • 16
  • 22
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • So if I'm reading correctly between the lines, you're saying that in .Net, let's say in a simple Console application, if you make an asynchronous I/O request, there's another thread involved and that's something that Ruby/Python want to avoid? I guess my confusion is that I always thought that when making an asynchronous I/O request .Net, there was no need for any additional threads. – BFree Jun 13 '14 at 20:16
  • @BFree, your follow-up is correct. In a simple console app, if you `await` the result of an async I/O API which you called on a thread without synchronization context (`SynchronizationContext.Current == null`), the `await` continuation callback will be called on a random thread where the operation has actually completed. Most likely, this is going to be an IOCP pool thread, different from the thread you started on. Check [this](http://stackoverflow.com/a/22475242/1768303) for some more details. If you don't like that, you have freedom to enroll your own sync. context like `AsyncPump`. – noseratio Jun 13 '14 at 23:07
  • 2
    There is a nice [example of event loop implementation](https://github.com/yuriy-nelipovich/single-sand) in .net, it has an example of console TCP server that is running on the main thread. All IOCP events are _redirected_ to the event loop so that it performs much like ruby. As Noseratio said, this is no way to get rid of this _redirection_. – neleus Nov 06 '14 at 10:03
2

EventMachine is not the only way to do asynchronous IO in Ruby. You could fork another process or spawn a new thread. Of course you would need to handle and coordinate the subsequent communication.

EventMachine is just one of the alternative concurrency implementations available to Ruby, based on the reactor pattern. It allows for non-blocking IO, without using any threads, in a single process. The main advantage is that you get rid of the complexity associated with multithreading, including deadlocks, race conditions, Ruby's infamous GIL, and debugging nightmares.

Async and Await in .NET offer a similar functionality which does not require multithreading, while Task.Run or BackgroundWorker follow a threaded approach.

As with every question worth pondering, each approach has its benefits and use cases. If you intend to perform a CPU-bound operation on the background you might be better off spawning a new thread. If you need to perform IO-bound operations then a reactor-based approach will be more suitable (and definitely less complex).

Kostas Rousis
  • 5,918
  • 1
  • 33
  • 38
  • 1
    _You could fork another process or spawn a new thread_ That's not asynchronous I/O. That's Synchronous I/O on a background thread. That thread will still block and then you have all the overhead of context switching etc. All operating systems have support for Asynchronous I/O and C#/.Net exposes that, whereas Ruby/Python does not. Why? That's my question. – BFree Jun 13 '14 at 20:12
  • @BFree it is still a form of async IO, see: https://en.wikipedia.org/wiki/Asynchronous_I/O#Forms – CMCDragonkai Oct 09 '16 at 04:49