Await HttpClient.GetStringAsync() and cancellation

I’m a big fan of supporting cancellation on async methods. I was recently wondering how to do this for HttpClient.GetStringAsync(). This post has some answers.

 

Async Function TestAsync(uri As Uri, Optional cancel As CancellationToken = Nothing

                        ) As Task(Of String)

    Dim client As New HttpClient

    Return Await client.GetStringAsync(uri, cancel)
    ' error: no overload of GetStringAsync takes a cancellation token

End Function

 

My first attempt is above. But it doesn’t compile, because HttpClient.GetStringAsync doesn’t have any overloads that take a CancellationToken.

 

The answer? Instead of using GetStringAsync, we have to use GetAsync, which does take a cancellation-token parameter:

 

Dim client As New HttpClient

Using response = Await client.GetAsync(uri,
                                       HttpCompletionOption.ResponseContentRead,
cancel)

    Return Await response.Content.ReadAsStringAsync()

End Using

The “ResponseContentRead” option can be omitted, since it is the default for calls to HttpClient.GetAsync. I included it just for clarity. What it means is that the entire body of the response is read by the call to HttpClient.GetAsync(), and so is subject to the cancellation token. Therefore the next call to ReadAsStringAsync() will complete immediately and needn’t worry about cancellation.

 

 

If you wanted to use the option “ResponseHeaderRead” instead, then cancellation looks different:

 

Try

    Using response = Await client.GetAsync(uri,

                              HttpCompletionOption.ResponseHeadersRead,

                              cancel)

        Using cancelreg = cancel.Register(Sub() response.Dispose())

            Return Await response.Content.ReadAsStringAsync()

        End Using

    End Using

Catch ex As ObjectDisposedException

    If cancel.IsCancellationRequested Then Throw New OperationCanceledException

    Throw

End Try

 

Explanation: in this case, only the headers of the response are read by HttpClient.GetAsync(), and so only they are subject to cancellation by the cancellation token. When we next invoke ReadAsStringAsync(), this operation might take a long time, and so we need to figure out how to cancel it. No overload of ReadAsStringAsync() takes a cancellation token. The best we can do is dispose of the HttpResponseMessage. This will cause the ReadAsStringAsync() method to terminate abruptly with an ObjectDisposedException.