OSDN Git Service

RefreshTimelineの呼び出し間隔の制御をThrottlingTimerクラスに抽出
authorKimura Youichi <kim.upsilon@bucyou.net>
Thu, 16 Aug 2018 19:28:04 +0000 (04:28 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Thu, 16 Aug 2018 19:28:04 +0000 (04:28 +0900)
OpenTween/OpenTween.csproj
OpenTween/ThrottlingTimer.cs [new file with mode: 0644]
OpenTween/Tween.cs

index 12a53e8..c8bc3d9 100644 (file)
     </Compile>
     <Compile Include="Setting\SettingManager.cs" />
     <Compile Include="ShortcutCommand.cs" />
+    <Compile Include="ThrottlingTimer.cs" />
     <Compile Include="Thumbnail\Services\PbsTwimgCom.cs" />
     <Compile Include="TweetDetailsView.cs">
       <SubType>UserControl</SubType>
diff --git a/OpenTween/ThrottlingTimer.cs b/OpenTween/ThrottlingTimer.cs
new file mode 100644 (file)
index 0000000..8891dfe
--- /dev/null
@@ -0,0 +1,90 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2018 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
+// for more details.
+//
+// You should have received a copy of the GNU General public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenTween
+{
+    /// <summary>
+    /// コールバック先の関数を <see cref="Interval"/> 未満の頻度で呼ばないよう制御するタイマー
+    /// </summary>
+    public class ThrottlingTimer : IDisposable
+    {
+        private const int TIMER_DISABLED = 0;
+        private const int TIMER_ENABLED = 1;
+
+        private readonly Timer throttlingTimer;
+        private readonly Func<Task> timerCallback;
+
+        private DateTimeUtc lastInvoked = DateTimeUtc.MinValue;
+        private DateTimeUtc lastExecuted = DateTimeUtc.MinValue;
+        private int refreshTimerEnabled = 0;
+
+        public TimeSpan Interval { get; }
+
+        public ThrottlingTimer(TimeSpan interval, Func<Task> timerCallback)
+        {
+            this.Interval = interval;
+            this.timerCallback = timerCallback;
+            this.throttlingTimer = new Timer(this.Execute);
+        }
+
+        public void Invoke()
+        {
+            this.lastInvoked = DateTimeUtc.Now;
+
+            if (this.refreshTimerEnabled == TIMER_DISABLED)
+            {
+                lock (this.throttlingTimer)
+                {
+                    if (Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_ENABLED, TIMER_DISABLED) == TIMER_DISABLED)
+                        this.throttlingTimer.Change(dueTime: 0, period: Timeout.Infinite);
+                }
+            }
+        }
+
+        private async void Execute(object _)
+        {
+            var timerExpired = this.lastInvoked < this.lastExecuted;
+            if (timerExpired)
+            {
+                // 前回実行時より後に lastInvoked が更新されていなければタイマーを止める
+                Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_DISABLED, TIMER_ENABLED);
+            }
+            else
+            {
+                this.lastExecuted = DateTimeUtc.Now;
+
+                await this.timerCallback().ConfigureAwait(false);
+
+                // dueTime は Execute が呼ばれる度に再設定する (period は使用しない)
+                // これにより timerCallback の実行に Interval 以上の時間が掛かっても重複して実行されることはなくなる
+                lock (this.throttlingTimer)
+                    this.throttlingTimer.Change(dueTime: (int)this.Interval.TotalMilliseconds, period: Timeout.Infinite);
+            }
+        }
+
+        public void Dispose()
+            => this.throttlingTimer.Dispose();
+    }
+}
index 3c0a684..59987de 100644 (file)
@@ -274,6 +274,7 @@ namespace OpenTween
         private bool _colorize = false;
 
         private System.Timers.Timer TimerTimeline = new System.Timers.Timer();
+        private ThrottlingTimer RefreshThrottlingTimer;
 
         private string recommendedStatusFooter;
         private bool urlMultibyteSplit = false;
@@ -1107,6 +1108,10 @@ namespace OpenTween
             }
 
             //タイマー設定
+
+            this.RefreshThrottlingTimer = new ThrottlingTimer(TimeSpan.Zero,
+                () => this.InvokeAsync(() => this.RefreshTimelineInternal()));
+
             TimerTimeline.AutoReset = true;
             TimerTimeline.SynchronizingObject = this;
             //Recent取得間隔
@@ -1380,62 +1385,8 @@ namespace OpenTween
             await Task.WhenAll(refreshTasks);
         }
 
-        private TimeSpan refreshInterval = TimeSpan.Zero;
-        private DateTimeUtc lastRefreshRequested = DateTimeUtc.MinValue;
-        private DateTimeUtc lastRefreshed = DateTimeUtc.MinValue;
-        private System.Threading.Timer refreshThrottlingTimer = null;
-        private int refreshTimerEnabled = 0;
-
         private void RefreshTimeline()
-            => this.RefreshTimelineThrottling();
-
-        private void RefreshTimelineThrottling()
-        {
-            const int TIMER_DISABLED = 0;
-            const int TIMER_ENABLED = 1;
-
-            async void timerCallback()
-            {
-                var timerExpired = this.lastRefreshRequested < this.lastRefreshed;
-                if (timerExpired)
-                {
-                    // 前回実行時より後に lastRefreshRequested が更新されていなければタイマーを止める
-                    Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_DISABLED, TIMER_ENABLED);
-                }
-                else
-                {
-                    this.lastRefreshed = DateTimeUtc.Now;
-
-                    await this.InvokeAsync(() => this.RefreshTimelineInternal())
-                        .ConfigureAwait(false);
-
-                    // dueTime は timerCallback が呼ばれる度に再設定する (period は使用しない)
-                    // これにより RefreshTimeline の実行に refreshInterval 以上の時間が掛かっても重複して実行されることはなくなる
-                    lock (this.refreshThrottlingTimer)
-                        this.refreshThrottlingTimer.Change(dueTime: (int)this.refreshInterval.TotalMilliseconds, period: Timeout.Infinite);
-                }
-            }
-
-            if (this.refreshThrottlingTimer == null)
-            {
-                var newTimer = new System.Threading.Timer(_ => timerCallback());
-
-                // Timer インスタンスの生成が同時に複数行われた場合は先に代入された方を優先する
-                if (Interlocked.CompareExchange(ref this.refreshThrottlingTimer, newTimer, null) != null)
-                    newTimer.Dispose();
-            }
-
-            this.lastRefreshRequested = DateTimeUtc.Now;
-
-            if (this.refreshTimerEnabled == TIMER_DISABLED)
-            {
-                lock (this.refreshThrottlingTimer)
-                {
-                    if (Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_ENABLED, TIMER_DISABLED) == TIMER_DISABLED)
-                        this.refreshThrottlingTimer.Change(dueTime: 0, period: Timeout.Infinite);
-                }
-            }
-        }
+            => this.RefreshThrottlingTimer.Invoke();
 
         private void RefreshTimelineInternal()
         {