[英]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.