In general, both approaches - using multiple EasyUAClient object, or using just one - are correct. However, I would recommend using just one, unless there is a reason to use more; the reason could be e.g. that some settings that are available on the client object need to be set differently (which probably isn't your case).
The reason for this recommendation is that the EasyUAClient object is relatively heavy-weight in terms of resource consumption - each such object "eats" from available memory and threads etc. To illustrate why is that, consider that each of them has a dedicated thread and queue to serve the event notifications to your code; this thread and queue assures that even if your code "blocks" in the event handler, the QuickOPC code should remain unaffected. Internally, however, there is a single "source" of these events (the "engine") anyway. Therefore, having more EasyUAClient objects gives you more flexibility (for example, you know without extra coding that the event notifications cam from the server you subscribed to), but it comes at a cost. And there are other similar consideration that speak in favor of just one, or at least a small number, of EasyUAClient objects. In addition, at least in .NET Core, the threads come from a pool, and with a large number of them, there may be no more available at least temporarily, which could explain the behaviors you are observing. I am not saying that I know that it is the cause, but it can be.
There are no "hard-coded" limits to the number of servers or tags, in the sense you have asked.
So I would a) monitor the # of threads, just to get a picture of what is happening, b) recode to use just one EasyUAClient, and c) test with the current version (2021.3), just in case.