The most critical difference is that threads can yield anywhere. Events only yield in well-defined places. Sometimes that's useful, sometimes it isn't. For example a thread can yield in the middle of a native call to something like zlib. If zlib doesn't know about your event framework (and it doesn't) then it can't yield.