Yeah, in general I'd prefer creating fewer threads rather than more, unless it either can't be avoided or there a clear advantage to creating more threads. In fact, I wonder how hard it would be to use vkd3d_fence_worker_main() for this. Waiting for fences is a blocking operation, but it may not have to be, and in principle these waits are expected to complete quickly. That also depends on which issue we're trying to address here, of course...
As it was probably evident from my comment, I'm also in favor of using as fewer threads as possible. Still, I'm not sure that reusing the fence worker would be a good idea, since the fence worker has to wait on either a timeline semaphore or a fence. Maybe the descriptor worker could even be rewritten in order to use a timeline semaphore to synchronize (though I'm not sure if it's possible to implement the variable condition), but it doesn't look that the same can be done for plain fences. And I'm not aware of any reasonable way to wait at the same time on sempahores/fences and whatever vkd3d already abstracts for mutexes and condition variables.
(I could go on with a rant on why Vulkan has so many different blocking functions, `vkWaitSemaphores()`, `vkWaitForFences()`, `vkWaitForPresentKHR()`, `vkAcquireNextImageKHR()` and maybe others I'm not ware of, and not a way to uniformly wait and multiplex on them, but I'm sure I'm preaching to the choir; and we have to deal with it anyway, see for instance https://gitlab.winehq.org/wine/wine/-/merge_requests/3262#note_38797)