-1

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

  • 1
    1. Go async all the way. 2. You can cancel a `GetAsync` by passing in a `CancellationToken`. – Fildor Jul 06 '23 at 13:53
  • Hi, thanks for the reply. Passing a CancellationToken doesn't change the behavior. The error is printed after 20 seconds. – WhyImUser_user3290746 Jul 06 '23 at 13:54
  • `string result = await task.Content.ReadAsStringAsync();` - where does `task` emerge from suddenly? Is this your _actual_ code? – Fildor Jul 06 '23 at 13:54
  • @user3290746 CancellationToken works. You didn't post your actual code so we can't guess what's wrong. BTW if you just want to retrieve a string without checking the response you can use `HttpClient.GetStringAsync` – Panagiotis Kanavos Jul 06 '23 at 13:56
  • var httpResponse = await _client.GetAsync("/somepath_doesnt_matter"); thats the line which waits 20 seconds. Leaving await makes the return a Task, thats why i thought in the background it's a task. – WhyImUser_user3290746 Jul 06 '23 at 13:56
  • 1
    Since the Task that `GetRequest` returns is never awaited, all sorts of sus things can happen. – Fildor Jul 06 '23 at 13:58
  • 1
    `await GetRequest();` and `Start()` needs to be `async`. I mean honestly, you got a **warning** which you disabled `#pragma warning disable CS4014 // Because this call is not awaited,` why did you disable it? – Charlieface Jul 06 '23 at 14:01
  • @ Panagiotis Kanavos i added the full file. With the CancellationTokenSource setting to 3 seconds the error gets printed after 20 seconds. – WhyImUser_user3290746 Jul 06 '23 at 14:01
  • @Charlieface GetAsync is a function from the HttpClient class https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.getasync?view=net-7.0, the Start function is a Unity Monobehavior function. I cant make it async. It is called by Unity at the start of the application. – WhyImUser_user3290746 Jul 06 '23 at 14:04
  • 2
    `async void Start()` or maybe there is another way to use `async` in Unity I don't know. But you **must** `await` the call otherwise the continuation may never run. – Charlieface Jul 06 '23 at 14:06
  • @Charlieface (@0 WhyImUser_user3290746) yes you can! And this `async void Start` is how .. alternatively you can use someting like `Task.Run(async () => await GetRequest());` but then might need to dispatch callbacks into the main thread again – derHugo Jul 06 '23 at 14:22
  • 1
    `ReadAsStringAsync()` has overload to accept cancellation token too. – Sinatr Jul 06 '23 at 14:27
  • I rewrote the code not to use async/await but to use Task.Run(...).Result Here ist the code: https://pastebin.com/KJ4xeb2S. Also the waiting time from the server is reduced to 5 seconds. Timeout of the HttpClient is 500 ms. And here is the printout: [16:30:51] Log1 [16:30:56] EXCEPTION ============================ So it still waits for the server response before throwing the exception. – WhyImUser_user3290746 Jul 06 '23 at 14:33
  • @WhyImUser_user3290746 you use `Task.Run` but now suddenly have a `Thread` ... and `GetRequest` is still not awaited – derHugo Jul 06 '23 at 14:36
  • Moving things to a thread and still calling async functions without awaiting them changes nothing and it'll just confuse you even more. You need to start experimenting with small async functions in Visual Studio Community edition so you get an understanding of how async functionality works. There are no safe shortcuts to take, your code needs to handle async calls correctly. – xxbbcc Jul 06 '23 at 14:36
  • @derHugo, doent really matter. With thread or without. The thread is just there from other tests, forgot to remove it. Removing it doesnt change a thing. – WhyImUser_user3290746 Jul 06 '23 at 14:38
  • @xxbbcc There are no async/await now. Look here pastebin.com/KJ4xeb2S – WhyImUser_user3290746 Jul 06 '23 at 14:39
  • why don't you at least try and do what @Charlieface said and have `async void Start` and within do `await GetRequest` to at least avoid all this task and thread clutter you have now? And if it still doesn't fail fast enough you can always simply pack it into a wrapper task that correctly checks the cancellation token and throws (e.g. poll checking the task state and manually throw if the time is exceeded in a while loop) – derHugo Jul 06 '23 at 14:43
  • @derHugo, i tryed the async void Start, nothing changed. Packing the whole thing will still keep the original "Task" running which is waiting for the response. – WhyImUser_user3290746 Jul 06 '23 at 14:46
  • You need to do `await task.Content.ReadAsStringAsync(cts.Token);` also – Charlieface Jul 06 '23 at 14:50
  • There is synchronous `Send` method in .Net 5 (see [this answer](https://stackoverflow.com/a/66655958/1997232)), no idea how it pass to the `Unity`, but check other answers in the same topic. – Sinatr Jul 06 '23 at 14:56
  • Guys, 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: https://pastebin.com/Tk4KHNmh – WhyImUser_user3290746 Jul 06 '23 at 15:20

1 Answers1

1

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();
    }  
John Lord
  • 1,941
  • 12
  • 27