Your implementation is fine, but you are missing proper cleanup and shutdown handling: you currently exit when one direction ends but leave the other hanging, and you don’t always propagate close/error events to both sockets. The fix is to coordinate shutdown with Task.WhenAny + cancellation + Task.WhenAll, close both sockets gracefully in a central method, and forward the original MessageType (not always Text). You can also simplify by using a single generic PumpAsync(source, dest) method instead of duplicating Process1/2.