有没有办法取消或终止在C#中等待来自HTTP客户端响应的任务?

huangapple go评论75阅读模式
英文:

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

问题

由于我在过去两天进行了谷歌搜索,仍然无法解决:

我正在使用Unity和C#。我有一个HTTP客户端,它会向我的本地服务器发送异步的GET请求。这个服务器被设置为在发送响应之前始终等待一定的时间。这是为了模拟一些实际服务器不会立即响应的行为。

举个例子,让我们假设我的服务器在发送响应之前等待20秒。我的客户端设置为在3秒内超时。

现在,我假设会在3秒后因超时而抛出错误并取消请求,但事实并非如此。错误会在客户端接收到响应后,也就是在20秒后抛出。

问题在于“真实”的服务器并不会回复每个请求。也许是因为我发送请求得太快了?无论如何,这些请求永远不会得到回复,而且会一直存在。

我的客户端代码如下(Unity中的C#):

private HttpClient _client = new HttpClient(); // 创建http客户端
public void Start()
{
    _client.BaseAddress = new System.Uri("http://127.0.0.1:8000"); // 设置基本URL
    _client.Timeout = new System.TimeSpan(0, 0, 0, 0, 3000); // 设置超时为3秒,也可以是500毫秒
    GetRequest();
}
private async Task GetRequest()
{
    try
    {
        var httpResponse = await _client.GetAsync("/somepath_doesnt_matter"); // 发送请求并等待响应
        string result = await task.Content.ReadAsStringAsync(); // 读取字符串
        Debug.Log("完成"); // 一切正常
    } 
    catch (Exception e)
    {
        Debug.Log(e.Message); // 已取消 <--- 在20秒后打印,而不是3秒
    }
}

附注:使用CancellationTokenSource,在将其设置为3秒超时并将令牌传递给GetAsync函数会导致相同的行为。

是否有一种方法可以在超时后“立即”抛出错误并取消任务?

编辑:这里是完整的文件链接:https://pastebin.com/WJQjmngt

使用一个CancellationTokenSource,将其设置为在3秒后超时,并将一个令牌传递给GetAsync函数,会导致相同的行为。

编辑2:似乎Unity以某种原因进行了不同的处理。我在VS控制台应用程序中编写了代码,它按预期取消了。然而,在Unity中,它会等待响应才会抛出错误。我猜我会去Unity论坛询问出了什么问题。如果有人感兴趣,这里是代码和结果:pastebin.com/Tk4KHNmh

英文:

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(&quot;http://127.0.0.1:8000&quot;); // 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(&quot;/somepath_doesnt_matter&quot;); // send request and await response
        string result = await task.Content.ReadAsStringAsync(); // read the string
        Debug.Log(&quot;Done&quot;); // all is good
    } catch (Exception e)
    {
        Debug.Log(e.Message); // canceled &lt;--- 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(&quot;http://127.0.0.1:8000&quot;);
        _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(&quot;/somepath&quot;, cts.Token);
            string result = await task.Content.ReadAsStringAsync();
            Debug.Log(&quot;GOT:\n&quot; + result);
        }
        catch (Exception e){
            Debug.Log(&quot;EXCEPTION ============================&quot;);
            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

请注意,一旦您使用了您的httpclient,就不允许再更改它的超时时间。我怀疑您在设置超时值之前对其进行了某些操作。我还会改变您设置超时的方式,以使其更可读。

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

这段代码绝对有效(我从我的项目中取出了它),所以如果您遇到问题,那就是在设置超时之前使用了上下文。如果发生这种情况,它会被忽略。

您也可以通过诡计来强制触发超时异常。运行您自己的超时代码。下面的示例将在时间到期时手动引发超时异常。这应该在Unity中起作用,并强制调用退出。

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

以及事件:

public static async Task&lt;TResult&gt; WithTimeout&lt;TResult&gt;(this 
Task&lt;TResult&gt; task, TimeSpan timeout)
{
    if (task == await Task.WhenAny(task, Task.Delay(timeout)))
    {
        return await task;
    }
    throw new TimeoutException();
}
英文:

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&lt;TResult&gt; WithTimeout&lt;TResult&gt;(this 
Task&lt;TResult&gt; task, TimeSpan timeout)
    {
        if (task == await Task.WhenAny(task, Task.Delay(timeout)))
        {
            return await task;
        }
        throw new TimeoutException();
    }  

huangapple
  • 本文由 发表于 2023年7月6日 21:51:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76629568.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定