Your problem is a subtle one: the async
lambda you’re passing to PushStreamContent
is being interpreted as an async void
(because the PushStreamContent
constructor only takes Action
s as parameters). So there’s a race condition between your module/handler completing and the completion of that async void
lambda.
PostStreamContent
detects the stream closing and treats that as the end of its Task
(completing the module/handler), so you just need to be sure there’s no async void
methods that could still run after the stream is closed. async Task
methods are OK, so this should fix it:
private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
Func<Stream, Task> copyStreamAsync = async stream =>
{
using (stream)
using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
{
await sourceStream.CopyToAsync(stream);
}
};
var content = new PushStreamContent(stream => { var _ = copyStreamAsync(stream); });
return content;
}
If you want your proxies to scale a bit better, I also recommend getting rid of all the Result
calls:
//Controller entry point.
public async Task<HttpResponseMessage> PostAsync()
{
using (var client = new HttpClient())
{
var request = BuildRelayHttpRequest(this.Request);
//HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
//As it begins to filter in.
var relayResult = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var returnMessage = BuildResponse(relayResult);
return returnMessage;
}
}
Your former code would block one thread for each request (until the headers are received); by using async
all the way up to your controller level, you won’t block a thread during that time.