关于C# Network Stream客户端与服务端的性能限制问题
Hey James, let's dig into why your load test is crashing—this setup has a handful of common .NET network programming pitfalls that are likely the culprit. Here are the most probable issues and fixes:
1. Unmanaged Resource Leaks (Network Streams & Timers)
Each connection creates a NetworkStream and a 250ms Timer, but if you're not explicitly cleaning these up when connections drop or fail, you'll quickly exhaust system resources like file handles or memory. Unclosed streams and orphaned timers will pile up until the process can't allocate more resources and crashes.
Fix:
- Wrap
NetworkStreaminstances inusingblocks (or implementIDisposableon your connection object) to ensure they're disposed when no longer needed. - Always stop and dispose timers when a connection closes to prevent lingering callbacks.
- Example connection cleanup pattern:
public class ServerConnection : IDisposable { private readonly NetworkStream _stream; private readonly Timer _sendTimer; private bool _isDisposed; public ServerConnection(NetworkStream stream) { _stream = stream; _sendTimer = new Timer(SendData, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(250)); } private async void SendData(object state) { if (_isDisposed) return; try { var payload = new byte[5000]; // Populate your data here await _stream.WriteAsync(payload, 0, payload.Length); } catch (IOException ex) { // Handle broken connection Dispose(); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_isDisposed) return; if (disposing) { _sendTimer.Stop(); _sendTimer.Dispose(); _stream.Dispose(); } _isDisposed = true; } }
2. Thread Pool Overload from Timer Callbacks
500 connections × 4 timer triggers per second = 2000 concurrent callback attempts every second. If your callbacks are doing synchronous work (like blocking NetworkStream.Write), the thread pool will get swamped with blocked threads. Eventually, the thread pool can't spin up more threads to handle work, leading to deadlocks or unhandled exceptions that crash the process.
Fix:
- Replace synchronous writes with async methods like
WriteAsyncto free up thread pool threads immediately. - Consider batching timer logic instead of per-connection timers—use a single timer that iterates over active connections and sends data asynchronously.
3. Unhandled Exceptions in Timer Callbacks
In .NET Framework, unhandled exceptions in System.Timers.Timer callbacks will crash the entire process outright. Even in .NET Core/.NET 5+, unhandled exceptions will terminate the timer thread and leave your connection in an inconsistent state, leading to cascading failures.
Fix:
- Wrap all timer callback logic in a
try-catchblock to catch network errors, object disposal exceptions, or other runtime issues. - Log exceptions instead of swallowing them so you can debug exactly what's failing.
4. Memory Pressure from Unbounded Connection Objects
500 concurrent connections each holding a 5000-byte buffer (plus overhead for streams, timers, and connection state) can add up quickly. If the garbage collector can't keep up with allocations, you'll hit an OutOfMemoryException and crash.
Fix:
- Use an object pool to reuse connection objects instead of creating new ones for every incoming connection.
- Limit the maximum number of concurrent connections your server accepts to avoid overwhelming memory resources.
Next Steps to Debug
- Check your application logs (or enable crash dumps) to see the exact exception that's causing the crash—this will narrow down the root cause instantly.
- Monitor system metrics during testing: track memory usage, thread count, and handle counts to spot leaks or overloads.
- Test with a smaller number of connections first (e.g., 50) and scale up gradually to isolate when the crash starts happening.
内容的提问来源于stack exchange,提问作者James Harcourt

