The crash is caused by concurrent access to the window. If you bother with threading, you need to understand that accessing objects the way you are doing it is dangerous and will almost always lead to undefined behaviour. The fact that you are blocking the main thread in the .waitEvent() call while still being able to call .close() on the window in a secondary thread shows you have no mutual exclusion set up. This was never supposed to work and never will, regardless whether we are talking about SFML objects or other C++ data structures.
If you want to close the window from the second thread, it will have to either do it itself when the first thread is not using the window or just "nicely tell" the first thread to do it. Either way, if you know you are going to have to do this, don't get yourself into the situation of having to unblock a thread from another. There have been numerous questions in the past about how one could cause a thread to resume operation without actually triggering the condition that would make it happen anyway, and the answer is always: don't get yourself into the situation in the first place. There is no well-defined way to do this. Even operating system facilities don't really have a standard way of getting this done.
Luckily for you, SFML provides you with 2 options for polling events, .waitEvent() and .pollEvent(). The former is really just a wrapper around the latter, which you can implement in your own code. If you have to check for a termination condition that the second thread might flag, then do so after .pollEvent() returns and close the window accordingly, it's really as simple as that. Also, if you think that this variant might be slower than .waitEvent(), internally, SFML polls for events and if none are inserted into the event queue, SFML itself sleeps for 10 milliseconds anyway. Saying that having to sleep yourself "is not the same" means that either you are just sleeping too long or are doing something else differently.
There really isn't any reason to use .waitEvent() in SFML, other than saving a few lines of code. Unlike GLFW, it doesn't save CPU time since it doesn't truly cause the process to block in some operating system call. In that sense, having a way to post an empty event to the event queue doesn't make as much sense in SFML as it does in other libraries.
I really wonder if the design of your application is a good one anyway. If you are going to have a second thread constantly running and deciding when the window in the first one is going to close, why even bother using .waitEvent() in the first one in the first place? It doesn't save you anything since the second thread will probably be constantly running anyway.