如何取消任务 - 尝试使用 CancellationTokenSource,但我想我不明白这个概念
Posted
技术标签:
【中文标题】如何取消任务 - 尝试使用 CancellationTokenSource,但我想我不明白这个概念【英文标题】:How to cancel a task - trying to use a CancellationTokenSource, but I think I don't get the concept 【发布时间】:2022-01-21 15:37:45 【问题描述】:我正在使用 VS2019 并创建一个 C# Windows 窗体应用程序 (.NET Framework) 和一个 C# 类库 (.NET Framework),两者都使用 .NET Framework 4.7.2。我的应用程序的主要目标是与 MSFS2020 上的 SimConnect 交互。
(c) 原代码来自Dragonlaird on the MSFS forum
当我连接 SimConnect 时,我需要一个 WndProc“messagePump”,它是通过从 NativeWindow 类派生而来的。我的 Connect 方法创建了一个 Task,它正在创建 messagePump 并与 SimConnect 连接,传递 messagePump 的句柄(这是 NativeWindow 派生类)。之后,我使用 AutoResetEvent 向主线程发出 messagePump 正在运行的信号,然后再启动无休止的 Application.Run()。
当我断开连接时,通过停止 messagePump 并删除 AutoResetEvent 对象来完成一些清理工作。
到目前为止一切顺利。一切似乎都很好。
但我试图通过使用 CancellationTokenSource 来停止任务,我将其传递给 messagePump 任务。我希望通过调用 Cancel() 方法,任务会被杀死。但这似乎不起作用,因为如果我多次连接/断开连接,那么我会看到每次创建一个额外的任务(使用调试/窗口/任务)。所以取消完全没有效果。
我想我知道为什么了,因为网上所有的信息都在谈论“合作取消”,我认为这意味着任务本身需要定期检查是否已触发取消并在这种情况下退出(是“合作”)。但是由于 Application.Run() 完全阻止了我的任务,我无法再“控制”取消。
在相关代码下方(仅相关部分)。当我断开连接时如何处理我的任务,避免内存泄漏,最后甚至是性能问题。
namespace SimConnectDLL
internal class MessageHandler : NativeWindow
public event EventHandler<Message> MessageReceived;
public const int WM_USER_SIMCONNECT = 0x0402;
internal void CreateHandle()
CreateHandle(new CreateParams());
protected override void WndProc(ref Message msg)
// filter messages here for SimConnect
if (msg.Msg == WM_USER_SIMCONNECT && MessageReceived != null)
try
MessageReceived.DynamicInvoke(this, msg);
catch // If calling assembly generates an exception, we shouldn't allow it to break this process
else
base.WndProc(ref msg);
internal void Stop()
base.ReleaseHandle();
base.DestroyHandle();
public class SimConnectDLL
private static MessageHandler handler = null;
private static CancellationTokenSource source = null;
private static CancellationToken token = CancellationToken.None;
private static Task messagePump;
private static AutoResetEvent messagePumpRunning = new AutoResetEvent(false);
private static SimConnect simConnect = null;
public static bool IsConnected get; private set; = false;
public static void Connect()
Debug.WriteLine("SimConnectDLL.Connect");
if (source != null)
Disconnect();
source = new CancellationTokenSource(); // Is needed to be able to cancel the messagePump Task
token = source.Token;
token.ThrowIfCancellationRequested();
messagePump = new Task(RunMessagePump, token); // Create Task to run the messagePump
messagePump.Start(); // Start task to run the messagePump
messagePumpRunning = new AutoResetEvent(false); // Create Synchronization primitive allowing the messagePump Task to signal back that it is running
messagePumpRunning.WaitOne(); // Wait until the synchronization primitive signals that the messagePump Task is running
public static void Disconnect()
Debug.WriteLine("SimConnectDLL.Disconnect");
StopMessagePump();
// Raise event to notify client we've disconnected
SimConnect_OnRecvQuit(simConnect, null);
simConnect?.Dispose(); // May have already been disposed or not even been created, e.g. Disconnect called before Connect
simConnect = null;
private static void RunMessagePump()
Debug.WriteLine("SimConnectDLL.RunMessagePump");
// Create control to handle windows messages
if (!IsConnected)
handler = new MessageHandler();
handler.CreateHandle();
ConnectFS(handler);
messagePumpRunning.Set(); // Signals that messagePump is running
Application.Run(); // Begins running a standard application message loop on the current thread.
Debug.WriteLine("Application is running");
private static void StopMessagePump()
Debug.WriteLine("SimConnectDLL.StopMessagePump");
if (source != null && token.CanBeCanceled)
source.Cancel();
source = null;
if (messagePump != null)
handler.Stop();
handler = null;
messagePumpRunning.Close();
messagePumpRunning.Dispose();
messagePump = null;
private static void ConnectFS(MessageHandler messageHandler)
Debug.WriteLine("SimConnectDLL.ConnectFS");
// SimConnect must be linked in the same thread as the Application.Run()
try
simConnect = new SimConnect("RemoteClient", messageHandler.Handle, MessageHandler.WM_USER_SIMCONNECT, null, 0);
messageHandler.MessageReceived += MessageReceived;
catch (Exception ex)
// Is MSFS is not running, a COM Exception is raised. We ignore it!
Debug.WriteLine($"Connect Error: ex.Message");
...
【问题讨论】:
messagePumpRunning.WaitOne();
这将阻止您当前的线程
你犯的错误是,将cancellationtoken传递给任务不会给你取消任务的方法,但如果之前取消它不会启动任务。
有一个Application.Exit
方法,但我认为Application.Run
不适合您使用它的方式,您可以改为使用Task.Delay(TimeSpan.MaxValue, cancellationToken
来“阻止”线程直到取消令牌被调用。
【参考方案1】:
但由于 Application.Run() 完全阻止了我的任务,我无法再“控制”取消。
你是对的 - 如果你想以这种方式使用取消令牌,你将不得不循环使用 token.ThrowIfCancellationRequested()
或在 Application.Run()
- CancellationToken.IsCancellationRequested
内为你的循环提供一个额外的参数(我认为它是一些那里有一个循环)。
否则,您只会在首次使用 token.ThrowIfCancellationRequested()
时检查一次令牌的状态。
另外,在您当前的应用程序状态下,如果您执行两次或更多次Connect
,则无法取消,因为您在连接时会覆盖您的CancellationTokenSource
。
首先检查CTS是否为null,如果不是,则取消,然后创建新的取消令牌源。
【讨论】:
@The Fabio - 感谢您的干预,但我不明白。我不能就我得到的答案要求更多的澄清吗?在这种情况下我需要创建一个新问题吗?上述答案需要澄清的是,我如何重写我的一段代码以允许它取消任务? @HansBilliet,我相信您应该编辑原始问题或向其中添加 cmets 而不是发布答案。但是,由于您似乎是 Stack Overflow 的新手,您可能还无法做到这一点 @HansBilliet 要在使用 CTS 时获得正确的结果,您必须将所述令牌传递给 Application.Run() 方法或将其传递给 Application 对象的构造函数 - 我假设所述对象/方法使用某种循环来完成工作,因此在这个循环中,ThrowIfCancellationRequested()
方法将被启动。【参考方案2】:
CancellationTokenSource
对象需要在异步任务中处理TaskCanceledException
,如下面的示例代码所示(仅供参考);现在,我似乎明白你无法控制异步任务内部发生的事情;但是,看起来在您的代码中您正在覆盖 messagePump
变量的值,所以最后,您不会将 CancellationToken
传递给异步任务
messagePump = new Task(RunMessagePump, token);
messagePump = new Task(RunMessagePump);
以下示例代码(再次:仅供参考)运行一个异步任务(Test
方法),每秒写入控制台,然后在五秒后停止
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Test
class Program
static void Main()
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task.Run(() => Test(cancellationToken));
Task.Delay(5000).Wait();
cancellationTokenSource.Cancel();
async static Task Test(CancellationToken cancellationToken)
while (true)
try
Console.WriteLine("The task is running");
await Task.Delay(1000, cancellationToken);
catch (TaskCanceledException)
break;
【讨论】:
感谢您的反馈(不知道规则是使用评论 - 从现在开始会这样做)。我已经删除了 messagePump 的额外分配。这是一些测试的剩余部分,但即使我删除它,我的任务也不会被取消。我会看看你的代码构造是否会帮助我,但我担心主要问题是我必须调用 Application.Run(),它正在启动我的 MessageHandler(需要一个用于 SimConnect 的 WndProc)。但是一旦 Application.Run() 被调用(这是 Windows 核心的东西),我就无法控制其中发生的事情,所以不能使用 Cancellationtoken。以上是关于如何取消任务 - 尝试使用 CancellationTokenSource,但我想我不明白这个概念的主要内容,如果未能解决你的问题,请参考以下文章