【问题标题】:Keep .NET console application alive until the termination sequence finishes保持 .NET 控制台应用程序处于活动状态,直到终止序列完成
【发布时间】:2018-08-19 14:28:42
【问题描述】:

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

所以,我编写了(基于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();
}

【问题讨论】:

  • @RosdiKasim 很抱歉,但像您这样将问题标记为与所讨论的问题无关的问题重复的人绝对会毁了这个网站......
  • 可能这个SO post能给你一些提示。看起来操作系统给你的最长时间是 5 秒来完成所有的清理工作。
  • @Thangadurai 特定超时由 .NET 运行时控制。如果他使用 win32 API 控制台方法,超时为 30 秒。如果他决定上传数据,两者都不是很好。我正在写一个建议来编写一个应该让他完全控制的 Windows 服务,但是Task 的答案要好得多。
  • 对此感到抱歉,但显然我不是唯一一个对这个问题感到困惑的人。此外,1 个标志不会让您的问题结束。也许你应该改写问题标题。

标签: c# .net terminate windows-console kernel32


【解决方案1】:

从 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();

应该这样做。

【讨论】:

  • 我喜欢这个答案,但我建议 OP 在用户注销或机器关闭等边缘情况下测试行为。我知道旧的 Win32 控制台 API 的处理方式非常不同。
  • 您应该改用_finalTask?.GetAwaiter().GetResult();。请参阅stackoverflow.com/questions/36426937/… 了解原因。
  • @CKII 作为记录:我确实尝试了您的解决方案,但它不起作用。为什么会呢? (也许我错过了一些东西。)但是,你做的事情和我做的一样,只是方式略有不同,因此它有同样的问题; OS/.NET 框架在它完成之前将其杀死。
  • 我看不出这有什么帮助。无论您使用任务、线程还是其他任何方式,操作系统仍然会以相同的方式终止进程。
猜你喜欢
  • 2017-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-13
  • 2016-08-21
相关资源
最近更新 更多