在重定向链

时间:2018-01-18 18:22:56

标签: c# asynchronous async-await timeout

我正在尝试以编程方式跟踪一系列重定向(针对在线广告像素),但是超时为2秒(换句话说,如果重定向链需要超过2秒才能解决,我希望它能够中止并返回null)。

我的代码(或多或少)同步运行,所以我不得不做一些杂技来做我想要的,但从功能上来说,它似乎工作......除了超时部分。

我有一些像这样的异步助手:

    public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
    {
        using (var timeoutCancellationTokenSource = new CancellationTokenSource())
        {
            var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
            if (completedTask != task)
            {
                throw new TimeoutException();
            }
            timeoutCancellationTokenSource.Cancel();
            return await task; 
        }
    }

    public static T ToSynchronousResult<T>(this Task<T> task)
    {
        return Task.Run(async () => await task).Result;
    }

TimeoutAfter()辅助方法改编自可以找到的SO文章here。在我的服务中,我有一个类似于此的方法:

    public string GetFinalUrl(string url)
    {
        string finalUrl;

        try
        {
            finalUrl = FollowDestinationUrl(url).TimeoutAfter(TimeSpan.FromSeconds(2)).ToSynchronousResult();
        }
        catch (TimeoutException)
        {
            finalUrl = null;
        }

        return finalUrl;
    }

    private async Task<string> FollowDestinationUrl(string url)
    {
        var request = _webRequestFactory.CreateGet(url);
        var payload = await request.GetResponseAsync();
        return payload.ResponseUri.ToString();
    }

此处_webRequestFactory返回一个写为IHttpRequest的HttpWebRequest抽象。

在我的成功案例单元测试中(2秒内的响应),我得到了我期望的结果:

    private class TestWebResponse : WebResponse
    {
        public override Uri ResponseUri => new Uri("https://www.mytest.com/responseIsGood");
    }

    [TestMethod]
    public void RedirectUriUnderTimeout()
    {
        //arrange
        var service = GetService();
        A.CallTo(() => _httpRequest.GetResponseAsync()).ReturnsLazily(() => new TestWebResponse());
        A.CallTo(() => _httpRequest.GetResponseString())
            .ReturnsLazily(() => VALID_REQUEST_PAYLOAD);

        //act
        var url = service.GetFinalUrl("https://someplace.com/testurl");

        //assert
        Assert.IsNotNull(url);
    }

...但是,当我尝试实施延迟以验证超时是否正常工作时,它并没有像我预期的那样中止:

    [TestMethod]
    public void RedirectUriUnderTimeout()
    {
        //arrange
        var service = GetService();
        A.CallTo(() => _httpRequest.GetResponseAsync()).ReturnsLazily(() => {
            Thread.Sleep(TimeSpan.FromSeconds(3));
            return new TestWebResponse();
        });
        A.CallTo(() => _httpRequest.GetResponseString())
            .ReturnsLazily(() => VALID_REQUEST_PAYLOAD);

        //act
        var url = service.GetFinalUrl("https://someplace.com/testurl");

        //assert
        Assert.IsNull(url);
    }

在返回具有非空TestWebResponse的{​​{1}}之前,似乎等待整整三秒钟。

我不知道我的实施是否存在根本性问题,或者我的测试是否有问题,但显然我以某种我不期待的方式阻止异步通话到。

有人可以帮助我找出我做错的事吗?

2 个答案:

答案 0 :(得分:1)

public static T ToSynchronousResult<T>(this Task<T> task)
{
    return Task.Run(async () => await task).Result;
}

这部分导致线程被阻塞。正如你提到的方法ToSynchronousResult,它将阻塞线程,直到返回任务结果。您应该一直按照&#34; async&#34; 规则进行操作,然后使用await。只有这样才能有效地应用async

public async Task<string> GetFinalUrl(string url)
{
    string finalUrl;

    try
    {
        finalUrl = await FollowDestinationUrl(url).TimeoutAfter(TimeSpan.FromSeconds(2));
    }
    catch (TimeoutException)
    {
        finalUrl = null;
    }

    return finalUrl;
}

答案 1 :(得分:0)

好吧,看起来我有点过分思考它。 @Stormcloak告诉我,我正在做的事情没有开始工作,所以我开始寻找替代品,我意识到虽然async / await模式在这里不合适,但是TPL库仍然存在方便。

我将FinalDestinationUrl方法改为同步,如下所示:

private string FollowDestinationUrl(string url)
    {
        var request = _webRequestFactory.CreateGet(url);
        var payload = request.GetResponse();
        return payload.ResponseUri.ToString();
    }

然后我这样称呼它:

var task = Task.Run(() => FollowDestinationUrl(destinationUrl));
finalUrl = task.Wait(TimeSpan.FromSeconds(2)) ? task.Result : null;

然后我将单元测试更改为:

[TestMethod]
public void RedirectUriUnderTimeout()
{
    //arrange
    var service = GetService();
    A.CallTo(() => _httpRequest.GetResponse()).ReturnsLazily(() => {
        Thread.Sleep(TimeSpan.FromSeconds(3));
        return new TestWebResponse();
    });
    A.CallTo(() => _httpRequest.GetResponseString())
        .ReturnsLazily(() => VALID_REQUEST_PAYLOAD);

    //act
    var url = service.GetFinalUrl("https://someplace.com/testurl");

    //assert
    Assert.IsNull(url);
}

测试通过了。一切都在世界上很好。谢谢!