Search code examples
c#unity-game-enginehttpclientcancellationtokensource

Is there way to cancel or kill a task which waits for a response from a http client in c#?


since i googled for the last 2 days and still can't figure it out:

I'm working with Unity and c#. I have a http client which sends async get requests to my local server. This server is set to always wait a certain amount of time until it sends a response. This is to simulate some behavior where the real server just doesn't answer.

For this example, let's say my server waits 20 seconds before sending a response. My client is set to timeout in 3 seconds.

Now i assumed that an error would be thrown after 3 seconds because of the timeout and the request is canceled but that's not the case. The error is thrown after the client receives a response, which is after 20 seconds.

The problem is that the "real" server doesn't answer every request. Maybe because i send requests to fast? Anyway, this requests are never answered and live forever.

My code for the client is as follow (C# in Unity):

private HttpClient _client = new HttpClient(); // create the http client
public void Start()
{
    _client.BaseAddress = new System.Uri("http://127.0.0.1:8000"); // set base url
    _client.Timeout = new System.TimeSpan(0, 0, 0, 0, 3000); // set the timeout to 3 seconds, could be 500 ms
    GetRequest();
}
private async Task GetRequest()
{
    try
    {
        var httpResponse = await _client.GetAsync("/somepath_doesnt_matter"); // send request and await response
        string result = await task.Content.ReadAsStringAsync(); // read the string
        Debug.Log("Done"); // all is good
    } catch (Exception e)
    {
        Debug.Log(e.Message); // canceled <--- prints after 20 seconds, not 3
    }
}

ps: using a CancellationTokenSource, setting it to timeout after 3 seconds and passing a token to the GetAsync function results in the same behavior.

Is there a way to "immediately" throw the error after the timeout and cancel the task?

Edit: here ist he full file: https://pastebin.com/WJQjmngt

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class Example1 : MonoBehaviour
{
    private HttpClient _client = new HttpClient();

    void Start()
    {
        _client.BaseAddress = new System.Uri("http://127.0.0.1:8000");
        _client.Timeout = new System.TimeSpan(0, 0, 0, 0, 3000);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        GetRequest();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
    }

    private async Task GetRequest()
    {
        try
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            cts.CancelAfter(3000);
            var task = await _client.GetAsync("/somepath", cts.Token);
            string result = await task.Content.ReadAsStringAsync();
            Debug.Log("GOT:\n" + result);
        }
        catch (Exception e){
            Debug.Log("EXCEPTION ============================");
            Debug.Log(e.Message);
        }
    }
}

Edit2: Seems like Unity do things different for whatever reason. I wrote the code in VS Console Application and it canceled as expected. In Unity however it waits for the response to throw the error. I guess i go to unity forums to ask whats wrong. Here the code and the results if someone is interested: pastebin.com/Tk4KHNmh


Solution

  • Be aware that once you've used your httpclient, you are no longer allowed to change the timeout on it. I suspect you used it for something before setting the timeout value. I would also change how you're setting your timeout so it's readable.

                try
                {
                    _client.Timeout = TimeSpan.FromSeconds(3);
                    HttpResponseMessage result = await client.GetAsync(uri);
                    string response = await result.Content.ReadAsStringAsync();
    
                }
                catch{ /*timeout*/}
    

    This code absolutely works ( i took it from my own project) so if you're having problems, you're using the context before setting the timeout. It gets ignored if that happens.

    You can also force a timeout exception by trickery. Run your own timeout code. The sample below will manually throw a timeout exception when the time expires. This should work in Unity and it forces the call to quit.

     await client.GetAsync()
    .WithTimeout(TimeSpan.FromSeconds(3))
    .ConfigureAwait(true);
    

    and the event:

    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();
        }