When you start a QThread it will run its own event loop. doStuff is the first slot that is executed in that event loop. However, by your design, the while-loop will block the event loop of this thread. The stop signal will thus never be executed as stop will be executed in the context of the receiver, i.e. the event loop of that other thread and not the main event loop.
Here are a few ideas how you could solve your problem:
Have the stop_action connect to a slot in MainWindow that directly calls self.worker.stop(). This will be executed in the main event loop, so blocking in the other thread is not a problem. Someone might mention that Worker.stopped is not an atomic variable. As long as you don't want to have an immediate reaction this will not be a problem. The worker thread will pick up the change eventually. (Actually, you might even not notice any delay.)
If you want to simulate an infinite loop inside the event loop of that thread, you can have a QTimer running with a timeout of 0s. This will constantly put a call to the same slot into your event loop. Still, the stop signal has a chance to slip in between and you'll have a chance to kill that timer.
As you said before you want to send data to the worker thread through a signal-slot connection. This approach would be the preferred way. Only do work in the worker thread when you have new data. Just call a slot in the worker thread (through a signal) with the new data and do the work. Afterwards the worker thread can idle to wait for new data. No need to have a loop running.