【问题标题】:How to use Task.Delay to control time spans between calls to a web service如何使用 Task.Delay 控制对 Web 服务的调用之间的时间跨度
【发布时间】:2013-06-15 17:10:52
【问题描述】:

当用户执行特定操作时,会调用(获取)网络服务。该服务不允许调用频率超过每秒一次。我在想我可以使用 Task.Delay 来控制它,以便后续调用至少相隔一秒钟,但它似乎没有按预期工作。 伪代码如下所示:

public async void OnUserAction()
{
    var timeUntilPreviousCallWillBeOrWasMade = 1000ms - (Now - previousWaitStartTime);
    var timeToWaitBeforeThisCallShouldBeMade = Max(0, timeUntilPreviousCallWillBeOrWasMade + 1000ms);
    previousWaitStartTime = Now;
    await Task.Delay(timeToWaitBeforeThisCallShouldBeMade);
    MakeCallToWebService();
}

调用和继续在同一线程上完成(由 Environment.CurrentManagedThreadId 报告)。 问题是,如果快速连续调用此方法,则调用 Web 服务之间经过的时间少于 1 秒。我要么犯了一个愚蠢的错误,要么我不完全理解 Task.Delay (或以上两者)。有什么提示吗?

【问题讨论】:

    标签: c# windows-runtime async-await winrt-async


    【解决方案1】:

    当另一个请求已经在等待时,当另一个请求排队时,您的问题就会出现。这是我对您的伪代码的看法:

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ThrottledAsync
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Queue up multiple user actions
                // within a short interval.
                for (var i = 0; i < 10; i++)
                {
                    OnUserAction();
                    Thread.Sleep(10);
                }
    
                Console.ReadLine();
            }
    
            private static int UserActionID;
            private static DateTime previousWaitStartTime;
    
            public static async void OnUserAction()
            {
                // Keep track of the operation ID.
                var userActionID = Interlocked.Increment(ref UserActionID);
    
                // Pseudo-code implementation.
                var timeUntilPreviousCallWillBeOrWasMade = 1000 - (int)(DateTime.Now.Subtract(previousWaitStartTime).TotalMilliseconds);
    
                Console.WriteLine(
                    "{0:HH:mm:ss.ffff} - User action {1}: timeUntilPreviousCallWillBeOrWasMade = {2}.",
                    DateTime.Now, userActionID, timeUntilPreviousCallWillBeOrWasMade);
    
                var timeToWaitBeforeThisCallShouldBeMade = Math.Max(0, timeUntilPreviousCallWillBeOrWasMade + 1000);
    
                Console.WriteLine(
                    "{0:HH:mm:ss.ffff} - User action {1}: timeToWaitBeforeThisCallShouldBeMade = {2}.",
                    DateTime.Now, userActionID, timeToWaitBeforeThisCallShouldBeMade);
    
                previousWaitStartTime = DateTime.Now;
    
                await Task.Delay(timeToWaitBeforeThisCallShouldBeMade);
                await MakeCallToWebService(userActionID);
            }
    
            private static async Task MakeCallToWebService(int userActionID)
            {
                // Simulate network delay.
                await Task.Delay(new Random().Next(5, 10));
    
                Console.WriteLine("{0:HH:mm:ss.ffff} - User action {1}: web service call.", DateTime.Now, userActionID);
            }
        }
    }
    

    还有输出:

    19:10:11.1366 - User action 1: timeUntilPreviousCallWillBeOrWasMade = -2147482648.
    19:10:11.1416 - User action 1: timeToWaitBeforeThisCallShouldBeMade = 0.
    19:10:11.1536 - User action 2: timeUntilPreviousCallWillBeOrWasMade = 988.
    19:10:11.1536 - User action 2: timeToWaitBeforeThisCallShouldBeMade = 1988.
    19:10:11.1586 - User action 1: web service call.
    19:10:11.1646 - User action 3: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.1646 - User action 3: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:11.1756 - User action 4: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.1756 - User action 4: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:11.1866 - User action 5: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.1866 - User action 5: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:11.1976 - User action 6: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.1986 - User action 6: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:11.2086 - User action 7: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.2086 - User action 7: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:11.2186 - User action 8: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.2196 - User action 8: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:11.2296 - User action 9: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.2296 - User action 9: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:11.2406 - User action 10: timeUntilPreviousCallWillBeOrWasMade = 990.
    19:10:11.2406 - User action 10: timeToWaitBeforeThisCallShouldBeMade = 1990.
    19:10:13.1567 - User action 2: web service call.
    19:10:13.1717 - User action 3: web service call.
    19:10:13.1877 - User action 5: web service call.
    19:10:13.1877 - User action 4: web service call.
    19:10:13.2107 - User action 6: web service call.
    19:10:13.2187 - User action 7: web service call.
    19:10:13.2187 - User action 8: web service call.
    19:10:13.2357 - User action 9: web service call.
    19:10:13.2537 - User action 10: web service call.
    

    您确实应该使用正确的工具来完成这项工作。 SemaphoreSlim 怎么样?

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ThrottledAsync
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Queue up simultaneous calls.
                MakeThrottledCall();
                MakeThrottledCall();
                MakeThrottledCall();
                MakeThrottledCall();
    
                Console.ReadLine();
            }
    
            // Used to throttle our web service calls.
            // Max degree of parallelism: 1.
            private static readonly SemaphoreSlim WebServiceMutex = new SemaphoreSlim(1, 1);
    
            private static async void MakeThrottledCall()
            {
                // Wait for the previous call
                // (and delay task) to complete.
                await WebServiceMutex.WaitAsync();
    
                try
                {
                    await MakeCallToWebService();
    
                    // Report the completion of your web service call if necessary.
    
                    // Delay for a bit before releasing the semaphore.
                    await Task.Delay(1000);
                }
                finally
                {
                    // Allow the next web service call to go through.
                    WebServiceMutex.Release();
                }
            }
    
            private static async Task MakeCallToWebService()
            {
                // Simulate network delay.
                await Task.Delay(new Random().Next(5, 10));
    
                Console.WriteLine("WebServiceCall: {0:HH:mm:ss.ffff}", DateTime.Now);
            }
        }
    }
    

    编辑:MakeThrottledCall 不再根据 svick 的评论返回 Task

    【讨论】:

    • 我不喜欢你的解决方案,因为 MakeThrottledCall() 应该在 MakeCallToWebService() 完成后立即完成(更准确地说,它返回的 Task 应该完成),而不是一秒钟后。
    • @svick,你说得很对。我对此没有很好的解决方案 - 只有丑陋的骇客,例如对Task.Delay(1000).ContinueWith(_ =&gt; Semaphore.Release()); 的即发即弃调用 所描述的解决方案通常不是异步节流的广泛适用模式。但是,它确实解决了问题所描述的具体情况(你会注意到原来的OnUserActionvoid),所以我现在坚持下去。
    • 感谢建议的替代解决方案,将尝试一下。但是,您对后续调用 Web 服务之间的时间的假设是不正确的。由于它表示为Max(0, timeUntilPreviousCallWillBeOrWasMade + 1000ms),因此在之前的调用尚未进行时,最新调用的等待时间将至少为 1000 毫秒(已经进行时则小于 1000 毫秒)。那么问题来了,为什么后续调用之间的时间有时会小于 1000ms?
    • @Christian,我错过了 + 1000 毫秒。然而,所有的变化只是第一次和第二次并发调用之间的时间——在那之后它们开始像机关枪一样开火。您只考虑前一个呼叫的等待开始,并没有为您提供完整的画面。当两个以上的请求彼此非常接近时,previousWaitStartTime 会多次设置为大致相同的值,并且您基于它进行计算,最终会导致多个延迟任务大致同时发生。
    • 啊,但当然,我现在觉得有点愚蠢 :) 我以为我只需要考虑之前的调用,这当然不是真的,因为我正在计算时间差异。我用++_callsPending; await Task.Delay(_callsPending * 1000); --_callsPending; 修改了你的代码,给出了更合理的数字。非常感谢!
    【解决方案2】:

    您可以在msdn 使用 Thread.Sleep(millisecondsTimeout) 吗?

    我建议你使用它。

    【讨论】:

    • Thread.Sleep 在 .NET for Windows Store Apps 中不可用。
    • ... 即使是这样,它也无法解决问题,因为 Task.Delay 和 Thread.Sleep 提供相同的功能(等待一定的时间),除了 Thread.Delay 和 Thread.Sleep 。 Sleep 会阻塞线程,这正是我们在使用 Tasks 和 async/await 结构时想要避免的原因。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多