webapi 2 - how to properly invoke long running method async/in new thread, and return response to client
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
If a Web API 2 request starts work that may take seconds or minutes, the controller should normally return quickly instead of keeping the HTTP connection open. Starting a raw thread inside the controller is usually the wrong solution because the IIS-hosted process can recycle, the thread is not durable, and failures are hard to observe.
The production-safe pattern is to accept the request, enqueue background work, return 202 Accepted, and give the client a job identifier or status URL. That makes the API responsive and the work lifecycle explicit.
Why Raw Threads in Controllers Are a Bad Fit
A controller method runs inside a hosted web process with its own lifecycle. If you call new Thread(...) or Task.Run(...) and return immediately, you have created background work that the web host does not really manage as durable application state.
That leads to familiar problems:
- app recycle can kill the work
- exceptions may disappear into logs or nowhere at all
- there is no retry or persistence boundary
- clients do not know how to track completion
A controller should validate input and schedule work, not own long-running background threads.
Return 202 Accepted With a Job ID
A better contract is to create a background job record and return immediately.
Now the API has a stable contract: accepted for processing, not finished yet.
Add Status Endpoints
Clients need a way to ask what happened to the job.
This design is much easier for browsers, mobile clients, and operations teams to reason about than “fire a thread and hope it finishes.”
Let a Worker Own the Long-Running Work
The background worker should update job state, capture errors, and handle retries.
This is the right place for retries, metrics, and job-level logging.
Idempotency Matters
If the client times out and retries the request, you do not want duplicate long-running jobs unless that is explicitly allowed.
Idempotency turns retries from a production problem into an expected control flow.
Common Pitfalls
A common mistake is thinking Task.Run inside a controller makes the work “background-safe.” It only moves work to another thread in the same fragile host process.
Another issue is returning 200 OK immediately without giving the client any way to learn whether the long-running operation later succeeded or failed.
Developers also sometimes skip durable job-state storage. Without it, status endpoints, retries, and operational visibility all become guesswork.
Finally, do not make the API pretend a background operation is synchronous when the user experience is actually asynchronous.
Summary
- Do not start unmanaged long-running threads inside Web API 2 controllers.
- Accept the request quickly, enqueue the work, and return
202 Accepted. - Give clients a job ID and status endpoint.
- Let a durable background worker own execution, retries, and failure tracking.
- Add idempotency so client retries do not create duplicate jobs.

