简体   繁体   中英

How to cancel a ReadLineAsync of StreamReader when reading from a Http stream?

I am doing a POST request to an uri, which returns me a stream of JSON data. This is done in a separate thread that is created at program start. If it matters, this is part of a Unity project. My code looks like this:

var request = new HttpRequestMessage(HttpMethod.Post, requestUri);

request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token.access_token);
request.Content = new StringContent(_jsonBody, Encoding.UTF8, "application/json");

HttpResponseMessage response = _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
    var stream = await response.Content.ReadAsStreamAsync();
    StreamReader reader = new StreamReader(stream);

    while (!_stopFlag && !reader.EndOfStream)
    {
        string line = await reader.ReadLineAsync();
        if (string.IsNullOrWhiteSpace(line)) continue;
        // Do stuff with data

The code works well when it is just waiting for the data to arrive and then handling it, but the method ReadLineAsync() of the StreamReader class will block until data arrives. This creates a problem whenever I want to exit the program gracefully with Thread.Join() , or if I want to restart the thread to send an altered version of the POST body. I have tried setting a flag to signal to my thread to exit, but this obviously does not work if a line of code inside the while loop i blocking.

During my research I have realized that using a cancellation token on ReadLineAsync() migth be hard. Solutions here on SO have suggested using some form of Wait() method on my HttpResponseMessage , but this will not work with a HTTP stream.

Is there something problematic with my implementation? How can I properly cancel ReadLineAsync() ?

This is a known problem with many of the async IO methods that they do not support cancellation or timeouts. What I do to get around it is to close the stream object you're reading from, which will cause an exception. It's not very elegant and it won't scale great if you see yourself cancelling a lot of read operations, but it works fine.

Here's a very simple example that illustrates the concept. The particulars will change a bit depending on the underlying object you're using if it's some sort of Stream or a.network client.

public void StartReceiving()
{
    _receivingCts = new CancellationTokenSource();
    _receivingTask = receivAsync(_receivingCts.Token);
}

// returns a task that completes when the receiving task completes
public async Task StopReceiving()
{
    try
    {
        _receivingCts?.Cancel();
        _stream?.Dispose(); //could also be a .Close() or similar
        await _receivingTask; 
    }
    catch
    {   
        // because _stream.ReceiveAsync doesn't take a cancellation
        // token, we end the receiving task by closing/disposing 
        // the stream while it is in use, this can generate an
        // exception
    }
    finally
    {
        _stream = null;
        _receivingCts = null;
        _receivingTask = null;
    }
}

private async Task ReceivAsync(CancellationToken token)
{
    try
    {
        _stream?.Close();
        _stream = /*create stream*/

        IsReceiving = true;
    
        while (!token.IsCancellationRequested)
        {
            var received = await _stream.Read****Async() 
                                        .ConfigureAwait(false);
            
            //do stuff with received;
        }
    }   
    catch (Exception ex)
    {   
        // if the token is cancelled, the exception is expected
        if (token.IsCancellationRequested)
            return; 
    }
    finally
    {
        IsReceiving = false;
    }
}

When .NET includes tokens/timeouts with async IO methods, the logic will be largely the the same.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM