C# 如何让任务提前完成
我的代码正在等待一段时间延迟:C# 如何让任务提前完成,c#,asynchronous,delay,C#,Asynchronous,Delay,我的代码正在等待一段时间延迟: await Task.Delay(10_000, cancellationToken); 在某些情况下,即使超时尚未过期,我也希望立即继续(“缩短延迟”)。此决定可在代码已在等待延迟时作出 我不想做的事情:按条件循环 foreach (var i = 0; i < 10 && !continueImmediately; i++) { await Task.Delay(1000, cancellationToken); } 因为我想
await Task.Delay(10_000, cancellationToken);
在某些情况下,即使超时尚未过期,我也希望立即继续(“缩短延迟”)。此决定可在代码已在等待延迟时作出
我不想做的事情:按条件循环
foreach (var i = 0; i < 10 && !continueImmediately; i++)
{
await Task.Delay(1000, cancellationToken);
}
因为我想避免例外
我想到了什么:
TaskCompletionSource tcs = new TaskCompletionSource();
await Task.WhenAny(new[] { tcs.Task, Task.Delay(10_000, cancellationToken) });
要缩短延迟时间:tcs.SetResult()
这似乎有效,但我不确定是否有任何清理遗漏。例如,如果采取了快捷方式(即由于tcs.Task
而完成的时间),则Task.Delay
是否会继续消耗资源
注意:我当然喜欢取消支持,但这不是我的问题,因此您可以忽略我问题中的所有取消令牌。您可以使用不传播异常的自定义:
public struct SuppressException : ICriticalNotifyCompletion
{
private Task _task;
private bool _continueOnCapturedContext;
/// <summary>
/// Returns an awaiter that doesn't propagate the exception or cancellation
/// of the task. The awaiter's result is true if the task completes
/// successfully; otherwise, false.
/// </summary>
public static SuppressException Await(Task task,
bool continueOnCapturedContext = true) => new SuppressException
{ _task = task, _continueOnCapturedContext = continueOnCapturedContext };
public SuppressException GetAwaiter() => this;
public bool IsCompleted => _task.IsCompleted;
public void OnCompleted(Action action) => _task.ConfigureAwait(
_continueOnCapturedContext).GetAwaiter().OnCompleted(action);
public void UnsafeOnCompleted(Action action) => _task.ConfigureAwait(
_continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(action);
public bool GetResult() => _task.Status == TaskStatus.RanToCompletion;
}
上面的
SuppressException
struct是裸体实现的一个稍微增强的版本,可以在GitHub post(由Stephen Toub发布)中找到。您也可以在这个答案的后面找到它。您可以使用自己的延迟方法:
public class DelayTask
{
private Timer timer;
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
private CancellationTokenRegistration registration;
private int lockObj = 0;
private DelayTask(TimeSpan delay, CancellationToken cancel)
{
timer = new Timer(OnElapsed, null, delay, Timeout.InfiniteTimeSpan);
registration = cancel.Register(OnCancel);
}
public static Task Delay(TimeSpan delay, CancellationToken cancel) => new DelayTask(delay, cancel).tcs.Task;
private void OnCancel() => SetResult(false);
private void OnElapsed(object state) => SetResult(true);
private void SetResult( bool completed)
{
if (Interlocked.Exchange(ref lockObj, 1) == 0)
{
tcs.SetResult(completed);
timer.Dispose();
registration.Dispose();
}
}
}
公共类延迟任务
{
私人定时器;
私有TaskCompletionSource tcs=新TaskCompletionSource();
私人取消登记;
私有int lockObj=0;
专用延迟任务(时间跨度延迟、取消令牌取消)
{
计时器=新的计时器(失效、空、延迟、超时.InfiniteTimeSpan);
注册=取消。注册(OnCancel);
}
公共静态任务延迟(TimeSpan Delay,CancellationToken cancel)=>新建延迟任务(Delay,cancel).tcs.Task;
私有void OnCancel()=>SetResult(false);
private void onelassed(对象状态)=>SetResult(true);
私有void SetResult(bool已完成)
{
if(联锁交换(参考锁定对象,1)==0)
{
tcs.SetResult(已完成);
timer.Dispose();
注册。处置();
}
}
}
据我所知,这与任务基本相同。延迟,但如果完成或未完成,则返回bool。请注意,它完全未经测试
这似乎有效,但我不确定是否有任何清理遗漏。例如,如果采取了快捷方式(即,由于tcs.Task,任务何时完成),任务延迟是否会继续消耗资源
可能不会,看吧。在大多数情况下,不需要处理任务。在最坏的情况下,由于需要最终确定,它们将导致一些性能损失。即使Task.Delay是在以后某个时间触发的,对性能的影响也应该是最小的。您可以编写自己的
Delay()
,它不会抛出在Task.Delay()
实现后建模的异常方法
下面是一个示例,如果取消令牌被取消,则返回true
;如果没有取消令牌,则返回false
(延迟正常超时)
这会打印类似于在00:00:02.0132319之后返回True的
如果您这样更改超时:
const int CANCEL1_TIMEOUT = 5000;
const int CANCEL2_TIMEOUT = 3000;
const int DELAY_TIMEOUT = 2000;
结果类似于00:00:02.0188434之后返回False的
作为参考,对于Task.Delay()
为什么您认为Task.Delay
会“运行”?它的目的是允许一个任务在不占用线程的情况下,在以后发出信号pointlessly@Damien_The_Unbeliever好的,“运行”是一个错误的术语,但我的观点是:它仍然会消耗资源吗?你以这样或那样的方式取消,那么另一个CTS和任务的意义是什么。什么时候有?如果不想等待,请不要调用Task.Delay()
:If(!shortcut){wait Task.Delay();}
。如果您想让步,请使用wait Task.yield()
,例如wait shortcut?Task.yield():Task.Delay(1000)
@PanagiotisKanavos在我的问题中更清楚地表明,在代码已经等待延迟的情况下,可能会做出选择捷径的决定。您是否试图避免取消引发的异常?您可以通过忽略异常的延续来实现这一点,例如Task.Delay(…).ContinueWith(=>{})代码>。谢谢你的链接,有趣的阅读!向上投票。顺便说一句,tokenSource2
是冗余的。您可以直接取消compositeTokenSource
。效果非常好-也谢谢您,不用使用任务。运行+线程。Sleep
组合来计划取消源代码,您可以改为使用以下方法:令牌源2.CancelAfter(CANCEL2\u超时)代码>
public class DelayTask
{
private Timer timer;
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
private CancellationTokenRegistration registration;
private int lockObj = 0;
private DelayTask(TimeSpan delay, CancellationToken cancel)
{
timer = new Timer(OnElapsed, null, delay, Timeout.InfiniteTimeSpan);
registration = cancel.Register(OnCancel);
}
public static Task Delay(TimeSpan delay, CancellationToken cancel) => new DelayTask(delay, cancel).tcs.Task;
private void OnCancel() => SetResult(false);
private void OnElapsed(object state) => SetResult(true);
private void SetResult( bool completed)
{
if (Interlocked.Exchange(ref lockObj, 1) == 0)
{
tcs.SetResult(completed);
timer.Dispose();
registration.Dispose();
}
}
}
// Returns true if cancelled, false if not cancelled.
public static Task<bool> DelayWithoutCancellationException(int delayMilliseconds, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
var ctr = default(CancellationTokenRegistration);
Timer timer = null;
timer = new Timer(_ =>
{
ctr.Dispose();
timer.Dispose();
tcs.TrySetResult(false);
}, null, Timeout.Infinite, Timeout.Infinite);
ctr = cancellationToken.Register(() =>
{
timer.Dispose();
tcs.TrySetResult(true);
});
timer.Change(delayMilliseconds, Timeout.Infinite);
return tcs.Task;
}
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace MultitargetedConsole
{
class Program
{
static async Task Main()
{
await test();
}
static async Task test()
{
const int CANCEL1_TIMEOUT = 5000;
const int CANCEL2_TIMEOUT = 2000;
const int DELAY_TIMEOUT = 10000;
using var tokenSource1 = new CancellationTokenSource(CANCEL1_TIMEOUT);
using var tokenSource2 = new CancellationTokenSource();
using var compositeTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource1.Token, tokenSource2.Token);
var compositeToken = compositeTokenSource.Token;
var _ = Task.Run(() => // Simulate something else cancelling tokenSource2 after 2s
{
Thread.Sleep(CANCEL2_TIMEOUT);
tokenSource2.Cancel();
});
var sw = Stopwatch.StartNew();
bool result = await DelayWithoutCancellationException(DELAY_TIMEOUT, compositeToken);
Console.WriteLine($"Returned {result} after {sw.Elapsed}");
}
}
}
const int CANCEL1_TIMEOUT = 5000;
const int CANCEL2_TIMEOUT = 3000;
const int DELAY_TIMEOUT = 2000;