web-dev-qa-db-ja.com

メソッドの実行を待って終了する方法は?

次のようなコンソールアプリケーション(dotnet core、ubuntu)があります。

void Main()
{
    try
    {
        var job = new Job();
        job.Start();

        while(Console.ReadLine() != "quit");
    }
    catch(Exception e)
    {
        //some handling
    }
}

JobJobAbstractクラスの実装ですが、

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コンテナーにまとめています。 ExecuteJobメソッドが実行されている間、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に送信する必要がありますか?実行フローを次のようにしたい:

  1. アプリは正常に実行されています。
  2. SIGTERMを受け取りました。 Executeが実行されていない場合は、アプリを停止します(実行されている場合)-終了するまで待ちます。
  3. Executeが終了したら、アプリを終了します。

この種のことをどのように実装すべきですか?それを達成するために、ある種のスキームまたはパターンをアドバイスしてください。前もって感謝します!


だから、私は何かを考え出した:public Task CurrentIteration { get; private set; }を追加し、Execute();を次のように変更するとどうなるか:

CurrentIteration = Execute();
await CurrentIteration;

サンプルを停止する際のstarting.Wait();の後に、次を追加します。

job.Stop();
await job.CurrentIteration;

うまくいきますか?次のアプローチに問題はありますか?

5
nicks

コードを見ると、アプリケーションのロジック全体が単一の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 を見ると、あなたのアプローチはそれを処理する実際の方法にすぎないようです。私が提案するのと同じようにそれをやっている一人の男さえいます。

2
Euphoric