C#
TcpListener
BeginAccept
ObjectDisposedException
networking

C TcpListener.BeginAccept and ObjectDisposedException

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

ObjectDisposedException with TcpListener.BeginAccept usually appears during shutdown races where accept loops continue after listener disposal. The core fix is controlled lifecycle management and cancellation-aware accept handling. Reliable server loops separate normal stop behavior from true errors.

A robust approach should remain reliable under maintenance, deployment, and troubleshooting pressure. Clear assumptions and repeatable validation steps are as important as the initial fix.

Core Sections

1. Structure accept loop with explicit running state

Use a stop token or state flag and avoid recursive accept callbacks that outlive listener scope. This makes shutdown deterministic and easier to reason about.

csharp
1private TcpListener _listener;
2private CancellationTokenSource _cts;
3
4public async Task StartAsync(int port)
5{
6    _cts = new CancellationTokenSource();
7    _listener = new TcpListener(IPAddress.Any, port);
8    _listener.Start();
9
10    while (!_cts.Token.IsCancellationRequested)
11    {
12        TcpClient client = await _listener.AcceptTcpClientAsync(_cts.Token);
13        _ = HandleClientAsync(client, _cts.Token);
14    }
15}

Start with a minimal implementation and validate one expected success path before adding complexity. This gives a stable baseline for later hardening.

2. Handle shutdown as expected control flow

On stop, cancel first, then stop listener. Catch disposal and cancellation exceptions as normal shutdown outcomes, not fatal errors.

csharp
1public void Stop()
2{
3    _cts?.Cancel();
4    _listener?.Stop();
5}
6
7private async Task HandleClientAsync(TcpClient client, CancellationToken ct)
8{
9    using (client)
10    using var stream = client.GetStream();
11    // process protocol
12    await Task.CompletedTask;
13}

Once baseline behavior is correct, harden around edge conditions and explicit error handling. This stage usually determines production reliability.

3. Differentiate real failures from stop events

Log shutdown transitions distinctly from unexpected accept errors. This avoids noisy alerts and improves operational confidence during planned restarts.

Include one edge-case and one failure-path check in automation so regressions are caught early. Operational readiness should also include focused telemetry and rollback planning before release.

Document ownership and runbook steps near the code path so on-call responders can reproduce and mitigate issues quickly under pressure.

Implementation quality also depends on operational clarity. Define expected inputs, boundary conditions, and explicit failure semantics so callers know what to do when results are unavailable or partially successful. Without this contract, different parts of the system often apply inconsistent assumptions, which creates subtle defects that are expensive to diagnose during incident response.

Automated validation should include one representative production-like scenario, one malformed-input case, and one dependency-failure case. Keep these tests deterministic and fast so they run on every change. Repeated CI execution is the most effective way to catch regressions introduced by refactoring, dependency upgrades, or environment changes that are not obvious from local ad hoc testing.

Observability is part of the design, not an afterthought. Log key branch decisions with concise context, include correlation identifiers where available, and track metrics tied directly to user impact such as latency percentiles and error categories. Focused telemetry helps responders distinguish code defects from infrastructure issues quickly and keeps troubleshooting time bounded.

Before release, prepare rollback and fallback behavior explicitly. Feature toggles, staged rollout, and clear reversion procedures reduce risk when assumptions fail under real traffic. Teams that plan recovery in advance can ship improvements more confidently and restore service faster when unexpected conditions occur.

Capture post-release metrics against baseline values and record findings so future changes can build on measured evidence rather than assumptions.

Common Pitfalls

  • Stopping listener without canceling accept loop first.
  • Treating disposal exceptions during shutdown as critical faults.
  • Using callback recursion that outlives listener lifecycle.
  • Ignoring cancellation tokens in client handler tasks.
  • Failing to serialize start and stop calls in hosting code.

Summary

  • Use cancellation-aware accept loops instead of unmanaged callbacks.
  • Cancel before stopping listener to reduce race conditions.
  • Treat disposal on shutdown as expected path.
  • Keep start and stop lifecycle transitions explicit.

Course illustration
Course illustration

All Rights Reserved.