Task.Delay(0)不是异步的。

16

我有以下代码(是的,我可能在模拟JavaScript setTimeout API)

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

看起来当timeout > 0时,setTimeout异步工作,控制权在await时返回给callee,并且callback在任务异步运行后被调用。但是timeout == 0时,函数以同步方式运行(代码总是在同一线程中通过await行执行而没有上下文切换)。进一步研究发现,Task.Delay的实现方式就是这样的 (Task.Yield() versus Task.Delay(0))

想知道是否有办法使Task.Delay(0)异步,或者有替代方案可以使我的 setTimeout 函数在timeout0时异步?(这样我就可以模仿JavaScript的setTimeout功能) 我看到有讨论使用 Task.FromResult(true)Task.WhenAll,但它们似乎不起作用。

确实可以使用Task.Delay(1)代替Task.Delay(0),但它看起来不自然。


当你说你正在寻找“上下文切换”时,你想要什么? - Justin Niessner
4
使用Task.Yield,它将把任务的继续处理方式交给TaskScheduler来处理,这正如另一篇文章所演示的那样。如果你想强制使用一个新线程,可以使用Task.Delay(timeout).ConfigureAwait(false) - Mike Zboray
关于模仿JavaScript功能,你会遇到很大的困难。据我所知,JavaScript是完全同步的,而setTimeout本质上是将方法排队到引擎正在处理的队列的末尾(假设timeout=0)。即使Delay为0超时分派了一个线程,它也不会像JavaScript一样运行。 - Rob
使用1代替0有什么问题吗?Task.Delay(1)的行为基本上与您从0期望的相同(因为它仍然小于最小时间量),并且执行将被调度回上下文(即对于WinForms/WPF,将被调度回UI线程)。 - Alexei Levenkov
创建一个0延迟的计时器似乎有点浪费,但它可能会做你想要的事情,尽管最好在tick事件回调内部处理它。你也可以将回调放在0延迟的Task.Run中(可能需要使用ConfigureAwait(false))。 - Mike Zboray
显示剩余9条评论
1个回答

24

Task.Delay(0) 不会异步运行的原因是因为异步等待状态机显式地检查任务是否已完成,如果已完成,则同步运行。

您可以尝试使用 Task.Yield(),这将强制方法立即返回并在当前SynchronizationContext上恢复方法的其余部分。例如:

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

    callback();
}

请修复您的 else 部分,它还没有完成。Task.Yield 在哪里?;) - M.kazem Akhgary
谢谢,不确定发生了什么,但在我登录后它不知怎么就丢失了。 - NeddySpaghetti
4
如果timeout非常低,这个方法存在竞态条件,可能会导致同步行为。我建议使用await Task.Yield(); await Task.Delay(timeout); - Stephen Cleary
2
为什么回调函数不是一个 Action - Aron
3
@Aron 他一路走来都是 JavaScript :) - nawfal
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接