During the last 5 years where I wrote LOTS of concurrent code I was in various thought stages, where in each stage I had some different feeling of what the best model might be.
In the meantime I have the feeling that both threaded and single-threaded event-driven programming programming models work well, and that the main thing to avoid is mixing both.
E.g. I found that using callback based or push-style APIs (like Reactive Extensions) in multithreaded environments is not a great idea, since it often won't be obvious on which thread a callback will be running and which kind of synchronization is needed. Not using callbacks and instead waiting/blocking for results works better. In a pure singlethreaded environment it's generally easy to know what to expect from a callback or promise - it will be fulfilled from the same thread and the continuation will also run from the same thread. In a multithreaded environment it can get hard - e.g. a promise continuation (like Task.ContinueWith() in C#) could run in any thread depending on the setup. And blocking on promises (like with Task.Result) may be valid in some situations (where the Promise is fulfilled from another thread) but not in others (where the Promise is fulfilled from the same thread). Using only synchronous primitives in multithreaded environments (like it's common in Go) avoids these pitfalls and questions.
Sometimes having a singlethreaded eventloop even in multithreaded environment however makes sense, like e.g. for GUIs (and other applications which have lots of shared state). In that case it should be assured that all objects which are living on the eventloop should just be accessed from other objects on the same thread - and do not involved directly in function calls from other threads. If that's required posting events or function calls to the eventloop thread is a much saner approach then trying to add low-level multithreading synchronization primitives everywhere.