我正在尝试以编程方式跟踪一系列重定向(针对在线广告像素),但是超时为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}}之前,似乎等待整整三秒钟。
我不知道我的实施是否存在根本性问题,或者我的测试是否有问题,但显然我以某种我不期待的方式阻止异步通话到。
有人可以帮助我找出我做错的事吗?
答案 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)
我将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);
}
测试通过了。一切都在世界上很好。谢谢!