簡體   English   中英

在 Visual Studio 2019 調試器中運行時,為什么控制台應用程序在最后一條語句后不退出?

[英]Why doesn't console application exit after last statement when running in Visual Studio 2019 debugger?

我正在尋找一種異步友好的方式來等待 ctrl+c 退出 C# 控制台應用程序。 如果我直接運行編譯的二進制文件,下面的代碼就可以工作。 但是,如果我在調試器中運行它,輸入 ctrl+c 並單步執行,我將點擊程序的右大括號,但它不會退出。 如果我注釋掉等待,應用程序會正常完成,所以我認為這不是 VS 設置問題。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Console.WriteLine("Hello World");
        await UntilCancelled().ConfigureAwait(true);
        Console.WriteLine("Goodbye Cruel World");
    }

    /// <summary>
    /// Waits until Ctrl+c or Ctrl+Break is entered into the console
    /// </summary>
    /// <param name="cancellationToken">A passed in cancellation token can also cause the await to complete</param>
    /// <returns>True when cancelled</returns>
    public static Task UntilCancelled(CancellationToken cancellationToken = default)
    {
        var cts = new CancellationTokenSource();
        //link tokens if caller wants to have ability to cancel for other conditions
        if (default != cancellationToken)
            cts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);

        //Attach cancellation to event for exiting the console
        Console.CancelKeyPress += (sender, cpe) => cts.Cancel();

        var tcs = new TaskCompletionSource<bool>();
        cts.Token.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
        return tcs.Task;
    }
}

嘗試這個:

public static Task UntilCancelled(CancellationToken cancellationToken = default)
{
    var tcs = new TaskCompletionSource<bool>();
    cancellationToken.Register(() => tcs.TrySetResult(true));
    Console.CancelKeyPress += (sender, cpe) =>
    {
        // this will prevent windows from interfering with the shutdown process
        cpe.Cancel = true; 
        tcs.TrySetResult(true);
    };

    return tcs.Task;
}

我用這個測試了它:

var cts = new CancellationTokenSource();
Console.WriteLine("Started");
Task.Run(async () =>
{
    await Task.Delay(5000);
    cts.Cancel(false);
}).GetAwaiter();
await Test.UntilCancelled(cts.Token);
Console.WriteLine("Canceled");

這是一個非常有趣的問題。

在 Visual Studio 中,當您按下暫停調試按鈕時,您可以打開並查看活動任務和/或線程的列表。

我暫停了執行。

在此處輸入圖像描述

並雙擊線程

選擇“下載源代碼並繼續調試”

在此處輸入圖像描述

並來到了這段代碼:

在此處輸入圖像描述

根據評論和方法名稱,我得出的結論是,它與調試模式和執行未成功完成有關。 事實上,當我以 Ctrl+F5 啟動它時,即沒有附加調試器,它就可以工作了。

在此處輸入圖像描述

而且您還可以看到返回碼不是零,這也表示執行不成功。

因此,它可能與調試器有關,並且您取消了任務但沒有捕獲它。

更新1:

再往前走, 發現事件注冊終於調用SetConsoleCtrlHandler

https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler

並據此

返回值 如果 function 處理控制信號,它應該返回 TRUE。 如果它返回 FALSE,則使用此進程的處理程序列表中的下一個處理程序 function。

返回值是 Cancel...Args 參數的 Cancel 屬性。

他們還說

默認情況下,Cancel 屬性為 false,這會導致程序執行在事件處理程序退出時終止。 將其屬性更改為 true 指定應用程序應繼續執行。

對我來說,這看起來很混亂,但我想您需要將 Cancel 設置為 true,這表明應用程序處理了取消事件。

更新2:

我用 winapi SetConsoleCtrlHandler更改了控制台事件,並且發生了相同的行為。 如果在 ConsoleCtrlDelegate 中將 false 更改為 true,應用程序將成功退出。 所以我認為在調試模式下可能會有另一個事件處理程序阻止執行,但我不確定。

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Console.WriteLine("Hello World");
        await UntilCancelled();
        Console.WriteLine("Goodbye Cruel World");
    }

    [DllImport("kernel32.dll")]
    static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine,
   bool Add);

    // Delegate type to be used as the Handler Routine for SCCH
    delegate Boolean ConsoleCtrlDelegate(CtrlTypes CtrlType);

    // Enumerated type for the control messages sent to the handler routine
    enum CtrlTypes : uint
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }

    /// <summary>
    /// Waits until Ctrl+c or Ctrl+Break is entered into the console
    /// </summary>
    /// <param name="cancellationToken">A passed in cancellation token can also cause the await to complete</param>
    /// <returns>True when cancelled</returns>
    public static Task UntilCancelled()
    {
        var cts = new CancellationTokenSource();

        ConsoleCtrlDelegate handler = (cpe) =>
        {
            cts.Cancel();

            return false;
        };

        SetConsoleCtrlHandler(handler, true);

        var tcs = new TaskCompletionSource<bool>();

        cts.Token.Register(s => tcs.SetResult(true), null);
        return tcs.Task;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM