How can I set a timeout for an Async function that doesn't accept a cancellation token?

前端 未结 3 1964
有刺的猬
有刺的猬 2021-01-04 20:13

I have my web requests handled by this code;

Response = await Client.SendAsync(Message, HttpCompletionOption.ResponseHeadersRead, CToken);

相关标签:
3条回答
  • 2021-01-04 20:28

    Since returns a task, you can Wait for the Task which essentially is equivalent to specifying a timeout:

    // grab the task object
    var reader = response.Content.ReadAsStringAsync();
    
    // so you're telling the reader to finish in X milliseconds
    var timedOut = reader.Wait(X);  
    
    if (timedOut)
    {
        // handle timeouts
    }
    else
    {
        return reader.Result;
    }
    
    0 讨论(0)
  • 2021-01-04 20:35

    Have a look at How do I cancel non-cancelable async operations?. If you just want the await to finish while the request continues in the background you can use the author's WithCancellation extension method. Here it is reproduced from the article:

    public static async Task<T> WithCancellation<T>( 
        this Task<T> task, CancellationToken cancellationToken) 
    { 
        var tcs = new TaskCompletionSource<bool>(); 
        using(cancellationToken.Register( 
                    s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) 
            if (task != await Task.WhenAny(task, tcs.Task)) 
                throw new OperationCanceledException(cancellationToken); 
        return await task; 
    }
    

    It essentially combines the original task with a task that accepts a cancellation token and then awaits both tasks using Task.WhenAny. So when you cancel the CancellationToken the secodn task gets cancelled but the original one keeps going. As long as you don't care about that you can use this method.

    You can use it like this:

    return await Response.Content.ReadAsStringAsync().WithCancellation(token);
    

    Update

    You can also try to dispose of the Response as part of the cancellation.

    token.Register(Reponse.Content.Dispose);
    return await Response.Content.ReadAsStringAsync().WithCancellation(token);
    

    Now as you cancel the token, the Content object will be disposed.

    0 讨论(0)
  • 2021-01-04 20:41

    While you can rely on WithCancellation for reuse purposes, a simpler solution for a timeout (which doesn't throw OperationCanceledException) would be to create a timeout task with Task.Delay and wait for the first task to complete using Task.WhenAny:

    public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
    {
        var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously);
        return Task.WhenAny(task, timeoutTask).Unwrap();
    }
    

    Or, if you want to throw an exception in case there's a timeout instead of just returning the default value (i.e. null):

    public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
    {
        if (task == await Task.WhenAny(task, Task.Delay(timeout)))
        {
            return await task;
        }
        throw new TimeoutException();
    }
    

    And the usage would be:

    var content = await Response.Content.ReadAsStringAsync().WithTimeout(TimeSpan.FromSeconds(1));
    
    0 讨论(0)
提交回复
热议问题