0 votes
by (2.2k points)

I want to cancel a Read call that has a very long delay after establishing an HTTP connection.

Will it be possible to cancel quickly using Cancellation.Register(() => stream.Close()) as shown below?

Please let me know if there is a correct cancellation method.

Current method in use (simplified)

try
{
    var stream = response.GetResponseStream();
    using var registration = canceltoken.Register(()=> stream.Close());
    while(canceltoken.IsCancellationRequested == false)
    {
        var data = stream.Read(buffer, 0, length);
        ...
    }
}
catch(Exception) when (cancelToken.IsCancellationRequested)
{
    canceled = true;
}
catch(Exception ex)
{
    error = ex.Message;
}
Applies to: Rebex HTTPS

1 Answer

+1 vote
by (77.9k points)
selected by
 
Best answer

For cancelling a request/reponse you can use the WebRequest.Abort() method. The solution can look like this:

// create request
var request = creator.Create(url);

// register call to the Abort() method
canceltoken.Register(request.Abort);

try
{
    // work with Request/Response/ResponseStream as noramlly
    var response = request.GetResponse();
    // ...
}
catch (WebException ex)
{
    // check whether the request was cancelled or we got a different error
    if (ex.Status == WebExceptionStatus.RequestCanceled)
        canceled = true;
    else
        error = ex.Message;
}

Using the Abort() method allows you to cancel the request in any state (sending request, reading response headers, reading response data).

by (2.2k points)
If I cannot use `request.Abort` when the function receiving the HTTP response is different from the function receiving the data, would it be okay to use `stream.Close` instead?

I plan to use `request.Abort` where everything is processed at once, and `stream.Close` where processing is separated.
by (77.9k points)
In general yes. If you call the stream.Close() method, the existing HTTP(S) connection will be forcefully closed. The state of the ResponseStream will be undefined and you will receive the ObjectDisposedException when accessing it.
ago by (2.2k points)
I have conducted various tests.

When registering Stream.Close in Cancellation, there are occasional cases where the Read call returns normally upon cancellation.
In such instances, data corruption sometimes occurs.

For example, there was a case where the Read call returned 100, but the data was incorrect.

Therefore, I implemented a method where, when there is a return value after a Read, I check if the call was cancelled, and if it was cancelled, I do not process the data; doing so resolves the issue.

Tested on Rebex version 7.0.9649.

Thank you
ago by (77.9k points)
When cancelling operations asynchronously there is always a race condition. When looking at your original sample code, it seems that the race leading to described issue is in your loop:

    while(canceltoken.IsCancellationRequested == false)
    {
        var data = stream.Read(buffer, 0, length);
        ...
    }

Please note that Read() can really return successfully at the moment of the cancellation. It is because the read operation was already completed and the Read() method was currently about to return a value. But the next Read() call would fail with an exception. However, it seems that your loop finishes successfully when the condition (canceltoken.IsCancellationRequested == false) is met, without attempt to call the next Read(), which would lead to an exception.

To solve the described issue, you can rewrite your loop like this:


    while (true)
    {
        if (canceltoken.IsCancellationRequested)
        {
            canceled = true;
            return; // returns with indication of cancel
        }

        var n = stream.Read(buffer, 0, length);
        if (n == 0)
        {
            return; // returns without cancel indication
        }
        ...
    }
...