使.NET控制台应用程序保持活动状态,直到终止序列完成

问题描述 投票:3回答:1

我正在研究数据采集应用程序,我想确保它优雅地退出。也就是说,它处理所有已经收集的数据,将所有(文件)缓冲区刷新到“磁盘”(持久性内存),甚至可能将数据上传到云端。

所以,我写了(基于this答案)下面的代码来捕捉每一个接近的事件。 (这只是一个测试代码。)

问题:如果我使用控制台右上角的X,即使终止序列仍在运行,程序也会在短暂延迟后终止。 (处理程序确实被调用,并且它确实开始等待线程加入但是它会在一段时间后被终止。)如果我终止于Crt + C或Ctr + Break它按预期工作;终止序列结束并退出该过程。

问题:如何让操作系统等待我的应用程序终止,而不是在短暂的宽限期后将其终止?

#region Trap application termination
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);

private delegate bool EventHandler(CtrlType sig);
static EventHandler _handler;

enum CtrlType
{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT = 1,
    CTRL_CLOSE_EVENT = 2,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT = 6
}

private static bool Handler(CtrlType sig, List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
    //starts new foregeound thread, so the process doesn't terminate when all the cancelled threads end
    Thread closer = new Thread(() => terminationSequence(threads, tasks, cancellationRequest));
    closer.IsBackground = false;
    closer.Start();

    closer.Join();  //wait for the termination sequence to finish

    return true; //just to be pretty; this never runs (obviously)
}
private static void terminationSequence(List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
    cancellationRequest.Cancel(); //sends cancellation requests to all threads and tasks

    //wait for all the tasks to meet the cancellation request
    foreach (Task task in tasks)
    {
        task.Wait();
    }

    //wait for all the treads to meet the cancellation request
    foreach (Thread thread in threads)
    {
        thread.Join();
    }
    /*maybe do some additional work*/
    //simulate work being done
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Console.WriteLine("Spinning");
    while (stopwatch.Elapsed.Seconds < 30)
    {
        if (stopwatch.Elapsed.Seconds % 2 == 0)
        {
            Console.Clear();
            Console.WriteLine("Elapsed Time: {0}m {1}s", stopwatch.Elapsed.Minutes, stopwatch.Elapsed.Seconds);
        }
        Thread.SpinWait(10000);
    }

    Environment.Exit(0); //exit the process
}
#endregion

static void Main(string[] args)
{
    CancellationTokenSource cancellationRequest = new CancellationTokenSource();    //cancellation signal to all threads and tasks
    List<Thread> threads = new List<Thread>(); //list of threads

    //specifys termination handler
    _handler += new EventHandler((type) => Handler(type, threads, new List<Task>(), cancellationRequest));
    SetConsoleCtrlHandler(_handler, true);

    //creating a new thread
    Thread t = new Thread(() => logic(cancellationRequest.Token));
    threads.Add(t);
    t.Start();
}
c# .net terminate windows-console kernel32
1个回答
2
投票

从C#7.1开始,您可以使用async Task Main()方法。使用它,您可以修改处理程序以创建方法,并在Main方法中等待它。

旁注:您应该尽可能使用任务而不是线程。任务管理你的线程更好,他们运行ThreadPool。当你创建一个新的Thread实例时,它假设它将是一个长时间运行的任务,并且windows将以不同的方式对待它。

因此,考虑到这一点,考虑将TerminateSequence方法包装在一个任务中,而不是一个线程中,并使该任务成为您的类的成员。现在你不必在处理程序的上下文中等待它,你可以在Main方法中等待它。

如果其余代码保持不变,您可以执行以下操作:

private Task _finalTask;

private static bool Handler(CtrlType sig, List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
    //starts new foregeound thread, so the process doesn't terminate when all the cancelled threads end
    _finalTask = Task.Run(() => terminationSequence(threads, tasks, cancellationRequest));
}

// ...

static async Task Main(string[] args)
{
    // ...

    // Wait for the termination process
    if(_finalProcess != null)
        await _finalTask
}

如果你不使用C#7.1,你仍然可以做到这一点,它只是不那么优雅。你需要做的就是等待它:

_finalTask?.Wait();

这应该做到这一点。

© www.soinside.com 2019 - 2024. All rights reserved.