动机
C# 5.0 async/await 构造非常棒,不幸的是,Microsoft只显示了 .NET 4.5 和 VS 2012 的候选版本,这些技术需要一些时间才能在我们的项目中得到广泛采用。
在 Stephen Toub 的异步方法、C# 迭代器和任务中,我找到了可以在 .NET 4.0 中很好地使用的替代方法。即使在 .NET 2.0 中,还有十几种其他实现也可以使用该方法,尽管它们看起来有点过时且功能不那么丰富。
例
所以现在我的 .NET 4.0 代码看起来像(注释部分显示了它在 .NET 4.5 中是如何完成的):
//private async Task ProcessMessageAsync()
private IEnumerable<Task> ProcessMessageAsync()
{
//var udpReceiveResult = await udpClient.ReceiveAsync();
var task = Task<UdpAsyncReceiveResult>
.Factory
.FromAsync(udpClient.BeginReceive, udpClient.EndReceive, null);
yield return task;
var udpReceiveResult = task.Result;
//... blah blah blah
if (message is BootstrapRequest)
{
var typedMessage = ((BootstrapRequest)(message));
// !!! .NET 4.0 has no overload for CancellationTokenSource that
// !!! takes timeout parameter :(
var cts
= new CancellationTokenSource(BootstrapResponseTimeout); // Error here
//... blah blah blah
// Say(messageIPEndPoint, responseMessage, cts.Token);
Task.Factory.Iterate(Say(messageIPEndPoint, responseMessage, cts.Token));
}
}
看起来有点丑,虽然它能完成工作
问题
在 .NET 4.5 中使用 CancelTokenSource 时,有一个构造函数将 timespan 作为超时参数,以便生成的CancellationTokenSource
在指定的时间段内取消。
.Net 4.0 无法超时,那么在 .Net 4.0 中执行此操作的正确方法是什么?
FWIW,你可以在 4.0 项目中使用 async/await,只需使用 async 目标包即可。 对我有用!
这真的与 async/await 有什么关系吗? 看起来您只需要一种方法来取消令牌,独立于异步/等待,对吧? 在这种情况下,您可以简单地创建一个在超时后调用取消的计时器吗?
new Timer(state => cts.Cancel(), null, BootstrapResponseTimeout, Timeout.Infinite);
编辑
我上面的初步反应是基本思想,但更强大的解决方案可以在 CancelTokenSource.CancelAfter() 泄漏吗?(实际上是您正在寻找的构造函数的 .Net 4.5 实现)。 下面是一个可用于基于该代码创建超时令牌的函数。
public static CancellationTokenSource CreateTimeoutToken(int dueTime) {
if (dueTime < -1) {
throw new ArgumentOutOfRangeException("dueTime");
}
var source = new CancellationTokenSource();
var timer = new Timer(self => {
((Timer)self).Dispose();
try {
source.Cancel();
} catch (ObjectDisposedException) {}
});
timer.Change(dueTime, -1);
return source;
}
您仍然可以使用 CancelAfter()
,这是Microsoft.Bcl.Async
中的一种扩展方法,与上面接受的答案非常相似。
这是我按 F12 查看CancelAfter()
实现时的引用代码:
/// <summary>Cancels the <see cref="T:System.Threading.CancellationTokenSource" /> after the specified duration.</summary>
/// <param name="source">The CancellationTokenSource.</param>
/// <param name="dueTime">The due time in milliseconds for the source to be canceled.</param>
public static void CancelAfter(this CancellationTokenSource source, int dueTime)
{
if (source == null)
throw new NullReferenceException();
if (dueTime < -1)
throw new ArgumentOutOfRangeException("dueTime");
Contract.EndContractBlock();
Timer timer = (Timer) null;
timer = new Timer((TimerCallback) (state =>
{
timer.Dispose();
TimerManager.Remove(timer);
try
{
source.Cancel();
}
catch (ObjectDisposedException ex)
{
}
}), (object) null, -1, -1);
TimerManager.Add(timer);
timer.Change(dueTime, -1);
}