【发布时间】: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来“阻止”线程直到取消令牌被调用。
标签: c# .net cancellationtokensource