to what nctnico and Siwastaja suggest above.
As an example, consider a text-based protocol similar to G-code or HPGL, where each command is optionally preceded by a numerical ID. (You can either accept any number within some range that is not already used in a pending command, or require consecutive IDs.) Commands that are executed immediately return the ID and success or failure, and commands that are handled asynchronously immediately return the ID and operation pending status, and completion status (success or failure) asynchronously afterwards. Example:
Request: 1 set pwm 50 Response: 1 ok: pwm 50 Request: get temperature Response: 2 pending Request: get frequency Response: 3 ok: frequency 2500 Response: 2 ok: temperature 298This way, you can also support a cancel request, for example
cancel all or
cancel 4; a
pending request to identify what is still pending; and a
wait/
wait all or
wait 4 command to synchronously wait until all or a specific command has completed before considering any new commands.
The underlying "trick" is to treat the connection as two separate unidirectional connections, with each request and response an atomic/uninterruptible message. The identifier means they do not need to stay synchronous: while a command is pending, additional commands can be served. You'll want commands that take enough time for other commands to be processed in the mean time to immediately respond that the command has been started but not completed yet.
Designing the exact protocol to be used is key, to ensure both sides (device and host) are efficient. Text-based protocols can be "easier" to implement (except for stuff related to newlines and whitespace), but binary protocols are just as reliable using USB Serial and if properly designed can be more efficient (simpler to parse, more compact). Because of this, I warmly recommend testing it in practice, creating throwaway firmwares with dummy commands and different protocols, until you find one that makes processing on both sides (device and host) robust/easy/efficient. I personally often play with such protocol ideas in Linux using Unix Domain datagram sockets (but not using the standard library for the communications otherwise).