Cancel task delay in .Net 4.0

I'm currently trying to implement a replacement for the .Net 4.5 Task.Delay() method in a program that should target .Net 4.0. I found the following code on this blog .

  /* You can write Task-based asynchronous methods by utilizing a TaskCompletionSource. A TaskCompletionSource gives you a 'slave' Task that you can manually signal. Calling SetResult() signals the task as complete, and any continuations kick off. */ void Main() { for (int i = 0; i < 10000; i++) { Task task = Delay (2000); task.ContinueWith (_ => "Done".Dump()); } } Task Delay (int milliseconds) // Asynchronous NON-BLOCKING method { var tcs = new TaskCompletionSource<object>(); new Timer (_ => tcs.SetResult (null)).Change (milliseconds, -1); return tcs.Task; } 

Tasks are completely new to me. System.Threading.Timer and TaskCompletionSource are completely new to me (today), and I'm struggling a bit with them. All this aside, I'm wondering how to add the CancellationToken functionality to this code. I assume I can add a parameter to the Delay() method as follows:

 Task Delay (int milliseconds, CancellationToken token) // Asynchronous NON-BLOCKING method { var tcs = new TaskCompletionSource<object>(); new Timer (_ => tcs.SetResult (null)).Change (milliseconds, -1); return tcs.Task; } 

... but then, where do I put the logic to check the token and exit the method? Somewhere in the callback? Is it possible?

+6
source share
3 answers

I tried to change my code as little as possible, but here is a working example that behaves the same as Task.Delay.

It is important to note that I use TrySetCanceled and TrySetResult , because the timer may end after the task is canceled. Ideally, you want to stop the timer.

Also note that a canceled task will raise a TaskCanceledException

 static void Main(string[] args) { // A cancellation source that will cancel itself after 1 second var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1)); try { // This will only wait 1 second because as it will be cancelled. Task t = Delay(5000, cancellationTokenSource.Token); t.Wait(); Console.WriteLine("The task completed"); } catch (AggregateException exception) { // Expecting a TaskCanceledException foreach (Exception ex in exception.InnerExceptions) Console.WriteLine("Exception: {0}", ex.Message); } Console.WriteLine("Done"); Console.ReadLine(); } private static Task Delay(int milliseconds, CancellationToken token) { var tcs = new TaskCompletionSource<object>(); token.Register(() => tcs.TrySetCanceled()); Timer timer = new Timer(_ => tcs.TrySetResult(null)); timer.Change(milliseconds, -1); return tcs.Task; } 

Read a little more in your question. If you need Task.Delay and you are targeting .NET 4.0, then you should use the Microsoft Async nuget package from http://www.nuget.org/packages/Microsoft.Bcl.Async/ , which contains the TaskEx.Delay method

+4
source

Like this :

 token.Register(() => tcs.TrySetCancelled()); 
+2
source

Here you are the version that prevents the garbage collector from deleting the timer

  public static Task Delay(int milliseconds, CancellationToken token) { var tcs = new TaskCompletionSource<object>(); var timer = new OneShotTimer((t) => { using ((OneShotTimer)t) tcs.SetResult(null); }); token.Register(() => { if (timer.TryCancel()) { using (timer) tcs.SetCanceled(); } }); timer.Start(milliseconds); return tcs.Task; } public class OneShotTimer : IDisposable { private readonly object sync = new object(); private readonly TimerCallback oneShotCallback; private readonly Timer timer; private bool isActive; public OneShotTimer(TimerCallback oneShotCallback, int dueTime = Timeout.Infinite) { this.oneShotCallback = oneShotCallback; this.isActive = dueTime != Timeout.Infinite; this.timer = new Timer(callback, this, dueTime, Timeout.Infinite); } public void Dispose() { timer.Dispose(); } public void Start(int dueTime) { if (!tryChange(true, dueTime)) throw new InvalidOperationException("The timer has already been started"); } public bool TryCancel() { return tryChange(false, Timeout.Infinite); } public bool tryChange(bool targetIsActive, int dueTime) { bool result = false; lock (sync) { if (isActive != targetIsActive) { result = true; isActive = targetIsActive; timer.Change(dueTime, Timeout.Infinite); } } return result; } private static void callback(object state) { var oneShotTimer = (OneShotTimer)state; if (oneShotTimer.TryCancel()) oneShotTimer.oneShotCallback(oneShotTimer); } } 
0
source

Source: https://habr.com/ru/post/982645/


All Articles