Turning synchronous channels asynchronous
(This is likely obvious, but since I keep working it out again in my head I'm going to write it down once and for all.)
Suppose that you have a CSP-like environment, with lightweight processes and synchronous communication channels with no buffering. Synchronous channels are simple but very inconvenient for many real-world things, where you need to have asynchronous channels. Fortunately, you can turn synchronous channels into acceptable asynchronous ones as follows.
To send an asynchronous message, you spawn a new process and write the message (along with the destination channel) to it through a new, dedicated channel. This will not block (barring scheduling issues), because you know the first thing that the new process does is read your message. The sub-process then writes the message to the real destination; while it may delay a very long time as it waits for the process on the other end of the channel to pick up your message to it, that's no longer a problem for your main process.
(This assumes that you can't pass any data to the new process except via a channel. If you can, you may be able to skip writing anything to it.)
I don't think you can do a pure asynchronous receive, but if the system doesn't already provide a 'select' operation you can implement it in a similar way. You have N persistent processes, each of which reads from a specific outside channel and then writes the received message to a common channel to your main process. Your main process then just reads the common channel and gets all events as they come in (although in some random order).
If the language is limited and type-safe enough that you can't write all of the messages to a single common channel, you need a slightly more elaborate scheme. Have a number of type-specific channels, and have the sub-processes first write a pointer message to the common channel and then retransmit the actual message to the type-specific channel. Your main process picks up the pointer from the common channel and then reads from the appropriate type-specific one; you may get a bit of blocking due to scheduling, but you should never stall waiting for a message.
(You might get into this situation if you are, say, a window system receiving keyboard events, mouse events, and messages from individual windows. The respective types may be so different that you can't smash them all into one structure and pass it over one channel.)
I will note in passing that while Go has channels, it allows them to be asynchronous (and has a powerful select operation).