Search code examples
.netc++-cli

How can I get DispatcherTimer to set a timer longer than 24 days?


My code uses System.Windows.Threading.DispatcherTimer to set a timer.

One day I found myself trying to set a larger timer than usual, and I got an ArgumentOutOfRangeException:

System.ArgumentOutOfRangeException: TimeSpan period must be less than or equal to Int32.MaxValue.

However, even in the 100ns units ("ticks") used by .NET TimeSpans, my value (roughly 41 days & 15½ hours, or 3,598,196,944 ms, or 3.5981969e+13 ticks — admittedly, a lot of ticks) ought to be well within the range of a 64-bit integer, which is what the documentation claims that TimeSpan uses internally (and which is used by the TimeSpan constructor taking ticks).

Since Int32.MaxValue is 2,147,483,647, it is suggested that DispatcherTimer only supports durations up to almost 25 days. But the DispatcherTimer documentation does not mention this limitation.

I ran some tests, and indeed:

#include <cstdint>

using namespace System;
using namespace System::Windows;
using namespace System::Windows::Threading;

void tick(System::Object^ sender, System::EventArgs^ args) {}

int main()
{
    // Duration is in 100ns units (10m per second).
    const TimeSpan delay1(2147483647ULL * 10000);

    const TimeSpan delay2(2147483648ULL * 10000);

    // Fine
    DispatcherTimer^ timer1 = gcnew DispatcherTimer(
        delay1,
        DispatcherPriority::Normal,
        gcnew EventHandler(&tick),
        Dispatcher::CurrentDispatcher
    );

    // System.ArgumentOutOfRangeException
    DispatcherTimer^ timer2 = gcnew DispatcherTimer(
        delay2,
        DispatcherPriority::Normal,
        gcnew EventHandler(&tick),
        Dispatcher::CurrentDispatcher
    );
}

I mean, okay, I can work around it by sequentially setting shorter delays (of, say, a maximum of a day at a time) but I'd like to avoid that complexity if possible.

Are my observations accurate? If so, is there any way to change this undocumented behaviour?

[Windows 10 (v1803), Visual Studio 2019 (v16.4.5), .NET 4.7.03056, x64 build]


Solution

  • When documentation fails, let's take a look at ye olde source code:

    https://referencesource.microsoft.com/#windowsbase/Base/System/Windows/Threading/DispatcherTimer.cs,94

    if (interval.TotalMilliseconds > Int32.MaxValue)
        throw new ArgumentOutOfRangeException("interval", SR.Get(SRID.TimeSpanPeriodOutOfRange_TooLarge));
    

    Your observation looks pretty on point to me. It also doesn't look like there's a good way to change this undocumented behavior without filing a ticket with Microsoft.

    Why would they put that seemingly artificial limitation in there? This line looks revealing enough:

    _dueTimeInTicks = Environment.TickCount + (int)_interval.TotalMilliseconds;
    

    Why refactor code to address corner cases when you can just validate the input and close the ticket? :)