InvalidOperationException: This instance has already started one or more requests. Properties can only be modified before sending the first request.
is thrown when you change a property such as Timeout or BaseAddress
on an HttpClient that has already sent a request. Set those properties once,
when the client is created, and leave them alone afterwards.
Move the property assignments into the object initializer, as in the fix below, so they are all set before the first request leaves the client.
The error
PS ...\Cli> dotnet run --no-build -- started
200 from /api/v1/patients: Maria Garcia, David Chen
Unhandled exception. System.InvalidOperationException: This instance has already started one or more requests. Properties can only be modified before sending the first request.
at System.Net.Http.HttpClient.CheckDisposedOrStarted()
at System.Net.Http.HttpClient.set_Timeout(TimeSpan value)
at Program.<Main>$(String[] args) in ...\Cli\Program.cs:line 40
at Program.<Main>(String[] args)
The code that threw:
using var http = new HttpClient();
http.BaseAddress = new Uri("http://127.0.0.1:15801/api/v1/");
Console.WriteLine(await http.GetStringAsync("patients"));
http.Timeout = TimeSpan.FromSeconds(30);
Why it happens
Setting BaseAddress before the first call worked; the first line of output is the
test server's answer. Setting Timeout after that call threw. The stack trace
names the check: set_Timeout calls HttpClient.CheckDisposedOrStarted,
and once the instance has sent a request the setter refuses the change. The message itself
states the rule: properties can only be modified before sending the first request.
In a larger program the two lines can sit far apart: a client kept in a field, and a
method that sets Timeout on it. The run above shows what decides the outcome:
a property set before the first request was accepted, and the one set after a request
was refused, so look for any assignment that can run after the client has been used.
The fix
using var http = new HttpClient
{
BaseAddress = new Uri("http://127.0.0.1:15801/api/v1/"),
Timeout = TimeSpan.FromSeconds(30),
};
Console.WriteLine(await http.GetStringAsync("patients"));
Console.WriteLine(await http.GetStringAsync("patients"));
PS ...\Cli> dotnet run --no-build -- started-fixed
200 from /api/v1/patients: Maria Garcia, David Chen
200 from /api/v1/patients: Maria Garcia, David Chen
Both properties are set in the initializer, before any request, and the same instance then sends two requests without complaint.
How it was reproduced
A console app (dotnet new console, no packages) on .NET SDK 10.0.403 called a
minimal API (dotnet new web) running on http://127.0.0.1:15801
with a GET route at /api/v1/patients. The failing run set
BaseAddress, made one successful GET, then assigned Timeout; the
fixed run set both in the initializer and made two GETs. The stack trace of the failing
run points at the Timeout assignment (set_Timeout, line 40 of
Program.cs), not at the GET before it, which had already returned its 200
answer from the test server.
Frequently asked
- How do I change HttpClient Timeout after the first request?
- Not on the same instance: in the test the setter threw InvalidOperationException after one request. Set Timeout when the client is created, in the object initializer.
- Why did my HttpClient code work once and then throw 'already started one or more requests'?
- In the test the sequence was: set BaseAddress, make one GET (it returned 200), set Timeout, and that last assignment threw. A property set before the first request was accepted; the one set after it was refused.
- Can I set BaseAddress on HttpClient before the first request?
- Yes. In the test, BaseAddress was assigned after the constructor and before the first GET, and the GET returned 200.
More decoded errors in the Fixes category. When the timeout you set does fire, the exception is a different one: TaskCanceledException from HttpClient.Timeout.