// zlib/libpng License
//
// Copyright (c) 2019 Sinoa
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace IceMilkTea.Core
{
#region リトライ抽象クラス
///
/// 再試行処理可能なワーカー抽象クラスです
///
public abstract class RetryableWorker
{
// クラス変数宣言
private static readonly Progress EmptyProgress;
///
/// RetryableWorker クラスの初期化をします
///
static RetryableWorker()
{
// 空進捗通知オブジェクトを生成しておく
EmptyProgress = new Progress();
}
#region DoWork関数
///
/// 指定された関数を実行します
///
/// 実行する同期関数
/// 指定された関数を実行しているタスクを返します
/// work が null です
public Task DoWork(Action work)
{
// 空進捗オブジェクトでキャンセルなしで同じ関数を呼ぶ
return DoWork(work, EmptyProgress, CancellationToken.None);
}
///
/// 指定された非同期関数を実行します
///
/// 実行する同期関数
/// 指定された関数を実行しているタスクを返します
/// work が null です
public Task DoWork(Task work)
{
// 空進捗オブジェクトでキャンセルなしで同じ関数を呼ぶ
return DoWork(work, EmptyProgress, CancellationToken.None);
}
///
/// 指定された値を返す関数を実行します
///
/// 返す値の型
/// 実行する同期関数
/// 指定された関数を実行しているタスクを返します
/// work が null です
public Task DoWork(Func work)
{
// 空進捗オブジェクトでキャンセルなしで同じ関数を呼ぶ
return DoWork(work, EmptyProgress, CancellationToken.None);
}
///
/// 指定された値を返す非同期関数を実行します
///
/// 返す値の型
/// 実行する非同期関数
/// 指定された関数を実行しているタスクを返します
/// work が null です
public Task DoWork(Task work)
{
// 空進捗オブジェクトでキャンセルなしで同じ関数を呼ぶ
return DoWork(work, EmptyProgress, CancellationToken.None);
}
///
/// 指定された関数を実行します
///
/// 実行する同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public Task DoWork(Action work, IProgress progress)
{
// キャンセルなしで同じ関数を呼ぶ
return DoWork(work, progress, CancellationToken.None);
}
///
/// 指定された非同期関数を実行します
///
/// 実行する同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public Task DoWork(Task work, IProgress progress)
{
// キャンセルなしで同じ関数を呼ぶ
return DoWork(work, progress, CancellationToken.None);
}
///
/// 指定された値を返す関数を実行します
///
/// 返す値の型
/// 実行する同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public Task DoWork(Func work, IProgress progress)
{
// キャンセルなしで同じ関数を呼ぶ
return DoWork(work, progress, CancellationToken.None);
}
///
/// 指定された値を返す非同期関数を実行します
///
/// 返す値の型
/// 実行する非同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public Task DoWork(Task work, IProgress progress)
{
// キャンセルなしで同じ関数を呼ぶ
return DoWork(work, progress, CancellationToken.None);
}
///
/// 指定された関数を実行します
///
/// 実行する同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// リトライのキャンセル要求を監視するためのトークン。既定は None です。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public async Task DoWork(Action work, IProgress progress, CancellationToken cancellationToken)
{
// 関数の実行開始を知らせる
BeginWork();
// 基本は無限ループ
var retryCount = 0;
while (true)
{
try
{
// 結果を拾って関数の実行が終わったことを通知して終了
work();
EndWork();
return;
}
catch (Exception error)
{
try
{
// エラーハンドリングをして正しくエラー解決出来なかった または リトライを諦められた場合は
if (!DoHandleError(error) || !await WaitRetry(cancellationToken))
{
// 関数の終了を通知して再スロー
EndWork();
throw;
}
}
catch
{
// エラーハンドリング側での例外は無視して関数の終了を通知して終了
EndWork();
return;
}
}
// ここに来たのなら、キャンセル要求を確認して、何もなければ
// リトライするという意志があるのでリトライプログレス通知をする
cancellationToken.ThrowIfCancellationRequested();
progress.Report(++retryCount);
}
}
///
/// 指定された非同期関数を実行します
///
/// 実行する同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// リトライのキャンセル要求を監視するためのトークン。既定は None です。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public async Task DoWork(Task work, IProgress progress, CancellationToken cancellationToken)
{
// 関数の実行開始を知らせる
BeginWork();
// 基本は無限ループ
var retryCount = 0;
while (true)
{
try
{
// 結果を拾って関数の実行が終わったことを通知して終了
await work;
EndWork();
return;
}
catch (Exception error)
{
try
{
// エラーハンドリングをして正しくエラー解決出来なかった または リトライを諦められた場合は
if (!DoHandleError(error) || !await WaitRetry(cancellationToken))
{
// 関数の終了を通知して再スロー
EndWork();
throw;
}
}
catch
{
// エラーハンドリング側での例外は無視して関数の終了を通知して終了
EndWork();
return;
}
}
// ここに来たのなら、キャンセル要求を確認して、何もなければ
// リトライするという意志があるのでリトライプログレス通知をする
cancellationToken.ThrowIfCancellationRequested();
progress.Report(++retryCount);
}
}
///
/// 指定された値を返す関数を実行します
///
/// 返す値の型
/// 実行する同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// リトライのキャンセル要求を監視するためのトークン。既定は None です。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public async Task DoWork(Func work, IProgress progress, CancellationToken cancellationToken)
{
// 関数の実行開始を知らせる
BeginWork();
// 基本は無限ループ
var retryCount = 0;
while (true)
{
try
{
// 結果を拾って関数の実行が終わったことを通知して終了
var result = work();
EndWork();
return result;
}
catch (Exception error)
{
try
{
// エラーハンドリングをして正しくエラー解決出来なかった または リトライを諦められた場合は
if (!DoHandleError(error) || !await WaitRetry(cancellationToken))
{
// 関数の終了を通知して再スロー
EndWork();
throw;
}
}
catch
{
// エラーハンドリング側での例外は無視して関数の終了を通知して終了
EndWork();
return default;
}
}
// ここに来たのなら、キャンセル要求を確認して、何もなければ
// リトライするという意志があるのでリトライプログレス通知をする
cancellationToken.ThrowIfCancellationRequested();
progress.Report(++retryCount);
}
}
///
/// 指定された値を返す非同期関数を実行します
///
/// 返す値の型
/// 実行する非同期関数
/// リトライ時の進捗通知オブジェクト。既定は空通知オブジェクトです。
/// リトライのキャンセル要求を監視するためのトークン。既定は None です。
/// 指定された関数を実行しているタスクを返します
/// work が null です
/// progress が null です
public async Task DoWork(Task work, IProgress progress, CancellationToken cancellationToken)
{
// 関数の実行開始を知らせる
BeginWork();
// 基本は無限ループ
var retryCount = 0;
while (true)
{
try
{
// 結果を拾って関数の実行が終わったことを通知して終了
var result = await work;
EndWork();
return result;
}
catch (Exception error)
{
try
{
// エラーハンドリングをして正しくエラー解決出来なかった または リトライを諦められた場合は
if (!DoHandleError(error) || !await WaitRetry(cancellationToken))
{
// 関数の終了を通知して再スロー
EndWork();
throw;
}
}
catch
{
// エラーハンドリング側での例外は無視して関数の終了を通知して終了
EndWork();
return default;
}
}
// ここに来たのなら、キャンセル要求を確認して、何もなければ
// リトライするという意志があるのでリトライプログレス通知をする
cancellationToken.ThrowIfCancellationRequested();
progress.Report(++retryCount);
}
}
#endregion
#region リトライ制御関数
///
/// DoWork() 関数によって処理される関数が実行される前に呼び出されます
///
protected virtual void BeginWork()
{
}
///
/// DoWork() 関数によって処理される関数が実行された後に呼び出されます
///
protected virtual void EndWork()
{
}
///
/// DoWork() 関数によって処理された関数が例外を発生させた時に呼び出されます
///
/// 発生した例外
/// 発生した例外を処理した場合は true を、処理できない例外だった場合は false を返します
protected virtual bool DoHandleError(Exception error)
{
// 既定は例外を処理したとして返す
return true;
}
///
/// 関数が失敗してリトライが必要な時にリトライするまでの待機をします
///
/// リトライのキャンセル要求を監視するためのトークン
/// リトライを待機するタスクを返します。リトライをしない場合は false を、リトライする場合は true を返します
protected abstract Task WaitRetry(CancellationToken cancellationToken);
#endregion
}
#endregion
#region 単純なカウントダウン式リトライワーカー
///
/// 単純なカウントダウン式による再試行処理可能なワーカークラスです
///
public class CountdownRetryableWorker : RetryableWorker
{
// メンバ変数定義
private int maxRetryCount;
private int currentCount;
///
/// 最大再試行カウント
///
public int MaxRetryCount
{
get
{
// 取得はそのまま返す
return maxRetryCount;
}
set
{
// 0未満は許さない
maxRetryCount = Math.Max(value, 0);
}
}
///
/// DoWork() 関数によって処理される関数が実行される前に呼び出されます
///
protected override void BeginWork()
{
// カウント数を初期化する
currentCount = maxRetryCount;
}
///
/// 関数が失敗してリトライが必要な時にリトライするまでの待機をします
///
/// リトライのキャンセル要求を監視するためのトークン
/// リトライを待機するタスクを返します。リトライをしない場合は false を、リトライする場合は true を返します
protected override Task WaitRetry(CancellationToken cancellationToken)
{
// もしリトライカウントがデクリメント結果で0未満になった場合は
if (--currentCount < 0)
{
// リトライは諦める結果を返す
return Task.FromResult(false);
}
// リトライする意志を返す
return Task.FromResult(true);
}
}
#endregion
#region 時間ベースのカウントダウン式リトライワーカー
///
/// 時間インターバルを持っているカウントダウン式再試行処理可能なワーカークラスです
///
public class TimeBasedCountdownRetryableWorker : RetryableWorker
{
// メンバ変数定義
private int maxRetryCount;
private int currentCount;
private int intervalTime;
///
/// 最大再試行カウント
///
public int MaxRetryCount
{
get
{
// 取得はそのまま返す
return maxRetryCount;
}
set
{
// 0未満は許さない
maxRetryCount = Math.Max(value, 0);
}
}
///
/// 再試行するまでの時間間隔(ミリ秒) 0 以下は待機をしません。
///
public int IntervalTime
{
get
{
// 値をそのまま返す
return intervalTime;
}
set
{
// 0未満は0にする
intervalTime = Math.Max(value, 0);
}
}
///
/// DoWork() 関数によって処理される関数が実行される前に呼び出されます
///
protected override void BeginWork()
{
// カウント数を初期化する
currentCount = maxRetryCount;
}
///
/// 関数が失敗してリトライが必要な時にリトライするまでの待機をします
///
/// リトライのキャンセル要求を監視するためのトークン
/// リトライを待機するタスクを返します。リトライをしない場合は false を、リトライする場合は true を返します
protected override async Task WaitRetry(CancellationToken cancellationToken)
{
// もしリトライカウントがデクリメント結果で0未満になった場合は
if (--currentCount < 0)
{
// リトライは諦める結果を返す
return false;
}
// 待機間隔時間が0ミリ秒を超過しているのなら
if (intervalTime > 0)
{
// 待機する
await Task.Delay(intervalTime, cancellationToken);
}
// リトライする意志を返す
return true;
}
}
#endregion
}