次のようなコンソールアプリケーション(dotnet core、ubuntu)があります。
void Main()
{
try
{
var job = new Job();
job.Start();
while(Console.ReadLine() != "quit");
}
catch(Exception e)
{
//some handling
}
}
Job
はJobAbstract
クラスの実装ですが、
public class JobAbstract
{
private readonly int _periodMs;
protected JobAbstract(int periodMs)
{
_periodMs = periodMs;
}
public bool Working { get; private set; }
public abstract Task Execute();
private void LogFatalError(Exception exception)
{
try
{
//logging
}
catch (Exception)
{
}
}
private async Task ThreadMethod()
{
while (Working)
{
try
{
await Execute();
}
catch (Exception exception)
{
LogFatalError(exception);
}
await Task.Delay(_periodMs);
}
}
public virtual void Start()
{
if (Working)
return;
Working = true;
Task.Run(async () => { await ThreadMethod(); });
}
public void Stop()
{
Working = false;
}
}
Job
は次のように定義されます:
public class Job : JobAbstract
{
public Job(periodMs) : base(periodMs)
{}
public override async Task Execute()
{
await SomeTask();
OtherKindOfJob();
await MaybeMoreAsyncTasks();
// etc
}
}
予想どおり、すべて正常に動作します。
今、私はそれをすべて継続的な配信のためのDockerコンテナーにまとめています。 Execute
のJob
メソッドが実行されている間、docker stop
がコンテナで実行される可能性があります。サイクルが終了するのを待って正常に終了するために、このアプローチを使用することにしました。
public static void Main(string[] args)
{
var ended = new ManualResetEventSlim();
var starting = new ManualResetEventSlim();
AssemblyLoadContext.Default.Unloading += ctx =>
{
System.Console.WriteLine("Unloding fired");
starting.Set();
System.Console.WriteLine("Waiting for completion");
ended.Wait();
};
System.Console.WriteLine("Waiting for signals");
starting.Wait();
System.Console.WriteLine("Received signal gracefully shutting down");
Thread.Sleep(5000);
ended.Set();
}
私はそれをテストし、それが機能しました.docker stop
を呼び出すと、DockerデーモンはSIGTERM
信号をコンテナーのプロセス#1(たまたま私のアプリ)に送信し、CoreCLRは適切に処理されるAssemblyLoadContext.Default.Unloading
イベントを呼び出します。
だから私は動作しているアプリとそれを(理論的には)止める方法を持っています。特定のコンテキストに対してどのように実装すればよいかわかりません。何らかのトークンをJob
に送信する必要がありますか?実行フローを次のようにしたい:
SIGTERM
を受け取りました。 Execute
が実行されていない場合は、アプリを停止します(実行されている場合)-終了するまで待ちます。Execute
が終了したら、アプリを終了します。この種のことをどのように実装すべきですか?それを達成するために、ある種のスキームまたはパターンをアドバイスしてください。前もって感謝します!
だから、私は何かを考え出した:public Task CurrentIteration { get; private set; }
を追加し、Execute();
を次のように変更するとどうなるか:
CurrentIteration = Execute();
await CurrentIteration;
サンプルを停止する際のstarting.Wait();
の後に、次を追加します。
job.Stop();
await job.CurrentIteration;
うまくいきますか?次のアプローチに問題はありますか?
コードを見ると、アプリケーションのロジック全体が単一のTask Execute()
として表現できると思います。そして、あなたが必要とするものはかなりシンプルです タスクキャンセル スキーム。
コードは次のようになります。
public static void Main(string[] args)
{
Start();
}
public static async void Start()
{
var cts = new CancellationTokenSource();
var starting = new ManualResetEventSlim();
AssemblyLoadContext.Default.Unloading += ctx =>
{
System.Console.WriteLine("Unloding fired");
cts.RequestCancellation();
System.Console.WriteLine("Waiting for completion");
ended.Wait();
};
// run application
try
{
await Execute(cts.Token);
}
catch(TaskCancellationException ex)
{
// cancelled
}
ended.Set();
}
私が気に入らないのは、Dockerからの停止信号をどのように処理するかです。アプリケーションが終了するまで、基本的にUnloading
イベントを「デッドロック」することは好きではありません。しかし、私はdockerの経験がなく、アプリケーションがSIGTERM
を使用して終了したときに「正常な」シャットダウンを処理する必要がなかったため、他に方法があるかどうかはわかりません。編集: this を見ると、あなたのアプローチはそれを処理する実際の方法にすぎないようです。私が提案するのと同じようにそれをやっている一人の男さえいます。