16

I have the following code (yes, I could be simulating JavaScript setTimeout api)

    async void setTimeout(dynamic callback, int timeout)
    {
        await Task.Delay(timeout);
        callback();
    }

It looks like for timeout > 0, the setTimeout works asynchronously wherein the control is returned back to the callee on await and callback is invoked after the task runs asynchronously. BUT when timeout == 0, the function behaves in a synchronous manner (code always run past the await line in the same thread without a context switch). On further digging, it turns out that Task.Delay is implemented to behave this way (Task.Yield() versus Task.Delay(0))

Wondering if there is a way to make Task.Delay(0) asynchronous or an alternate solution to make my setTimeout function asynchronous when timeout is 0? (so that I could mimic JavaScript setTimeout functionality) I see discussions on using Task.FromResult(true) or Task.WhenAll, but they don't seem to work.

Indeed I can use Task.Delay(1) instead of Task.Delay(0) but it does not look organic.

Community
  • 1
  • 1
infinity
  • 1,900
  • 4
  • 29
  • 48
  • What are you looking for when you say you're looking for a "context switch"? – Justin Niessner Oct 29 '15 at 06:08
  • I'm not really sure what you're trying to achieve. If you delay for `0ms`, you're not really delaying. In fact, its neither synchronous nor asynchronous as it's essentially a no-op. There is literally ***no work*** to be done asynchronously, so it immediately returns. – Rob Oct 29 '15 at 06:11
  • 4
    Use `Task.Yield`. This will post the continuation to be handled however the TaskScheduler sees fit, which that other post demonstrates. If you want to force a new thread use `Task.Delay(timeout).ConfigureAwait(false)`. – Mike Zboray Oct 29 '15 at 06:14
  • In regards to mimicking javascript functionality, you're going to have a rough time. As far as I'm aware, javascript is *completely* synchronous, and `setTimeout` essentially queues up the method at the end of the queue that the engine is processing (assuming `timeout=0`). Even *if* `Delay` did dispatch a thread for 0 timeout, it wouldn't behave the same was as javascript does. – Rob Oct 29 '15 at 06:15
  • @Rob I fully understand that the code will return immediately when `timeout` is 0 as there is no work to be done. But my question is help with emulating the JS `setTimeout` function in C#. **Not possible using Task.Delay** would be a perfectly fine answer to me :) – infinity Oct 29 '15 at 06:17
  • Hey @mikez, I will try your suggestion. Also what about spinning a timer and doing something like this ``` var timer = new System.Timers.Timer(delayInMilliseconds); timer.Elapsed += () => callback(); timer.AutoReset = false; timer.Enabled = true; timer.Start(); ``` – infinity Oct 29 '15 at 06:19
  • What's wrong with using 1 instead of 0? `Task.Delay(1)` would behave essentially the same as you'd expect from 0 (as it is still less then smallest time quant) and exectution will be scheduled back to context (i.e. to UI thread for WinForms/WPF). – Alexei Levenkov Oct 29 '15 at 06:19
  • @AlexeiLevenkov `Task.Delay(1)` works, but it does not look organic :( and I wanted to reach out to the community to probe other options!!! – infinity Oct 29 '15 at 06:20
  • Creating a timer for a 0 delay seems little wasteful but it would probably do what you want, although best to dispose it inside the tick event callback. You could also just put the callback inside a `Task.Run` (possibly with ConfigureAwait(false)) on 0 delay. – Mike Zboray Oct 29 '15 at 06:24
  • @infinity, as long as you've mentioned JS `setTimer`, do you care about the threading model of this code? It all depends on the ambient synchronization context, and well may continue on a different thread after `await Task.Delay()`. – noseratio Oct 29 '15 at 10:09
  • @infinity, you can also look at the source code too at [referencesource.microsoft.com](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,5863), this is not a solution, just a tip to understand these behaviours – Philippe Paré Oct 29 '15 at 15:33
  • @Noseratio Ideally I would like it to continue on the same thread. Buyt wouldn't execution after `await Task.Delay()` continue on the same thread unless we set `ConfigureAawit` is set to `false` ? – infinity Oct 29 '15 at 16:35
  • @PhilippeParé Thanks for the pointer!!! I didn't know that such a thing existed! – infinity Oct 29 '15 at 16:44
  • @infinity, it will under WPF or WinForms, but anywhere else (with default sync. context) it will continue on a random pool thread, which most likely is going to be different from the original thread. You might like to look at something like [`AsyncPump`](http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx). – noseratio Oct 29 '15 at 20:06

1 Answers1

24

The reason Task.Delay(0) does not run asynchronously is because the async-await state machine explicitly checks whether the task is completed and if it is, it runs synchronously.

You can try using Task.Yield(), which will force the method to return immediately and resume the rest of the method on the current SynchornizationContext. eg:

async void setTimeout(dynamic callback, int timeout)
{
    if(timeout > 0)
    {
        await Task.Delay(timeout);
    }
    else
    { 
        await Task.Yield();
    }

    callback();
}
NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
  • please fix your else part. its not completed. where is Task.Yield ? ;) – M.kazem Akhgary Oct 29 '15 at 06:20
  • thanks, not sure what happened, it somehow got lost after I logged in. – NeddySpaghetti Oct 29 '15 at 06:21
  • 4
    This has a race condition that could still cause synchronous behavior if `timeout` is very low. I recommend using `await Task.Yield(); await Task.Delay(timeout);`. – Stephen Cleary Oct 29 '15 at 12:02
  • 2
    why isn't callback an `Action`? – Aron Apr 01 '16 at 02:13
  • Main problem with `Task.Yield()` is that it is more like [`process.nextTick()` than `setImmediate()`](http://stackoverflow.com/a/15349865/429091). From the docs of [`Task.Yield()`](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.yield%28v=vs.110%29.aspx): “The synchronization context that is present on a UI thread in most UI environments will often prioritize work posted to the context higher than input and rendering work.”. – binki Nov 21 '16 at 23:17
  • can i use action instead of dynamic ? Can someone give an example about the usage of this function ? – Jaydeep Karena Oct 06 '18 at 08:45
  • 2
    @Aron he went javascript all the way :) – nawfal Nov 13 '20 at 16:26