.NET API ์†Œ๊ฐœ(1) : PeriodicTimer

์˜ค๋Š˜์€ .NET 6 ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” PeriodicTimer๋ฅผ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

PeriodicTimer๋Š” ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ํƒ€์ด๋จธ ํ‹ฑ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ตœ์‹  ํƒ€์ด๋จธ API ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ ์š”,

var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));

while (await timer.WaitForNextTickAsync())
{
    Console.WriteLine(DateTime.Now);
}

1์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

| ์‹คํ–‰ ๊ฒฐ๊ณผ

2021-11-18 ์˜คํ›„ 11:14:17
2021-11-18 ์˜คํ›„ 11:14:18
2021-11-18 ์˜คํ›„ 11:14:19
2021-11-18 ์˜คํ›„ 11:14:20
2021-11-18 ์˜คํ›„ 11:14:21

์–ผํ• ๋ณด๋ฉด Task.Delay()์˜ ๊ธฐ๋Šฅ๊ณผ ๋ณ„๋ฐ˜ ์ฐจ์ด๊ฐ€ ์—†์–ด ๋ณด์ด์ฃ ? ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์˜ ์ฝ”๋“œ๋ฅผ ๋ณด์‹œ์ฃ .

var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));

while (await timer.WaitForNextTickAsync())
{
    Console.WriteLine($"Wake Up!: {DateTime.Now}");

    // 1500 ms ์†Œ์š”๋˜๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •
    Thread.Sleep(1500);
}

| ์‹คํ–‰ ๊ฒฐ๊ณผ

Wake Up!: 2021-11-18 ์˜คํ›„ 11:18:56
Wake Up!: 2021-11-18 ์˜คํ›„ 11:18:58
Wake Up!: 2021-11-18 ์˜คํ›„ 11:19:00
Wake Up!: 2021-11-18 ์˜คํ›„ 11:19:02
Wake Up!: 2021-11-18 ์˜คํ›„ 11:19:04

๊ฐ•์ œ๋กœ 1500 ms ๋งŒํผ์˜ ๋”œ๋ ˆ์ด๋ฅผ ์คฌ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ง€์ •ํ•œ period ๋งŒํผ์˜ ํƒ€์ด๋จธ ํ‹ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํƒ€์ด๋จธ ๋™์ž‘ ์ค‘ ์ทจ์†Œํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์„๊นŒ์š”? WaitForNextTickAsync(cancellationToken)๋ฅผ ํ†ตํ•ด ์ทจ์†Œ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ข€ ๋” ์‰ฝ๊ฒŒ ์ทจ์†Œํ•˜๋Š” ๋ฐฉ๋ฒ•์€ timer๋ฅผ Disposeํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

using System.Diagnostics;

var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));

var task = Task.Run(async () =>
{
    var sw = Stopwatch.StartNew();
    while (await timer.WaitForNextTickAsync())
    {
        Console.WriteLine($"Wake Up!: {DateTime.Now} {sw.ElapsedMilliseconds}");

        // 1500 ms ์†Œ์š”๋˜๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •
        Thread.Sleep(1500);

        sw.Restart();
    }

    Console.WriteLine("ํƒ€์ด๋จธ๊ฐ€ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
});

Console.WriteLine("์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ํƒ€์ด๋จธ๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.");
Console.ReadLine();

timer.Dispose();

Console.WriteLine("์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.");

Console.ReadLine();

| ์‹คํ–‰ ๊ฒฐ๊ณผ

์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ํƒ€์ด๋จธ๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
Wake Up!: 2021-11-18 ์˜คํ›„ 11:21:59 2014
Wake Up!: 2021-11-18 ์˜คํ›„ 11:22:01 483
Wake Up!: 2021-11-18 ์˜คํ›„ 11:22:03 484
Wake Up!: 2021-11-18 ์˜คํ›„ 11:22:05 484
Wake Up!: 2021-11-18 ์˜คํ›„ 11:22:07 485
Wake Up!: 2021-11-18 ์˜คํ›„ 11:22:09 486
Wake Up!: 2021-11-18 ์˜คํ›„ 11:22:11 496
Wake Up!: 2021-11-18 ์˜คํ›„ 11:22:13 486

์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
ํƒ€์ด๋จธ๊ฐ€ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด ํƒ€์ด๋จธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์™„์ „ํžˆ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ ๊ฐ์ฒด ์ˆ˜๋ช… ๋ฌธ์ œ๋ผ๋˜๊ฐ€ ๋น„๋™๊ธฐ ์ฝœ๋ฐฑ์ด ์—†๋Š” ๋“ฑ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์„ ์—†์•จ ์ˆ˜ ์žˆ์–ด ์•ˆ์ „ํ•˜๊ฒŒ ํƒ€์ด๋จธ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ข‹์•„์š” 4

Task.Delay() ๋น„๊ต ์˜ˆ์‹œ์—์„œ
SynchronizationContext ํ• ๋‹น์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ• ๊นŒ์š”?

์˜ˆ๋ฅผ ๋“ค๋ฉด WPF ํ™˜๊ฒฝ์—์„œ

var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));

while (await timer.WaitForNextTickAsync())
{
    // ์ด ๊ตฌ๊ฐ„์€ dispatcher ์— ์˜ํ•ด ์ˆ˜ํ–‰.
    Console.WriteLine($"Wake Up!: {DateTime.Now}");

    Thread.Sleep(1500);
}

์ด ์ฝ”๋“œ๊ฐ€ ํ‘œ์‹œํ•ด์ฃผ์‹  ๊ฒฐ๊ณผ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋Š” ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

์ œ๊ฐ€ ์ง€๊ธˆ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์ด ์•„๋‹ˆ๋ผโ€ฆ ์ง์ž‘ํ•˜๊ธฐ๋กœ๋Š” await์ดํ›„์˜ ์ฝ”๋“œ๋“ค์ด SynchronizationContext ์˜ ์Šค๋ ˆ๋“œ๋กœ ๋™์ž‘ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š”๋ฐ, ๊ทธ๋Ÿฌ๋ฉด Thread.Sleep(1500)์—์„œ ํ™”๋ฉด์ด ๋ฉˆ์ถœ๋“ฏ ํ•ฉ๋‹ˆ๋‹ค. ์žˆ๋‹ค๊ฐ€ ์ €๋… ์ฆˆ์Œ์— ํ™•์ธํ•ด๋ณผ๊ป˜์š”.

์ข‹์•„์š” 2

์˜ˆ์ƒํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ SynchronizationContext์— ์˜ํ•ด ์ดํ›„ ์ฝ”๋“œ๋Š” SynchronizationContext์˜ ์Šค๋ ˆ๋“œ๋กœ ์‹คํ–‰์ด ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, 1500 ms ๋™์•ˆ ํ™”๋ฉด์ด ๋ฉˆ์ถฅ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด timer.WaitForNextTickAsync().ConfigureAwait(false)๋ฅผ ํ•ด์„œ SynchronizationContext๋ฅผ ๋ฌด์‹œํ•˜๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ข‹์•„์š” 2