C# 中止线程:此平台不支持线程中止

Posted

技术标签:

【中文标题】C# 中止线程:此平台不支持线程中止【英文标题】:C# Aborting a thread: Thread abort is not supported on this platform 【发布时间】:2021-08-21 20:55:44 【问题描述】:

问题

我在 c# 中创建了一个线程(在 windows 10 上),它向服务器请求一些数据:

每次用户进入某个菜单时都应该创建这样一个线程(mainMenuJoinRoomButtonClick()) 每次用户退出该菜单时都应该关闭它(我们使用roomListBackButtonClick()退出)

这样做是为了让我们节省资源防止线程未关闭时发生的错误。 (请看下面的代码)

我尝试使用threadName.Abort(),但它给了我以下错误:

System.PlatformNotSupportedException: 'Thread abort is not supported on this platform.'

我也试过threadName.Interrupt(),它会产生这个错误(错误出现在行:Thread.Sleep(3000);):

System.Threading.ThreadInterruptedException: 'Thread was interrupted from a waiting state.'

这里有什么问题,我该如何关闭线程?


研究完成

上网查了showed没有什么好的方法可以中止一个线程,是这样吗?

我还找到了使用Thread.sleep() 的this 答案(第一个),但这似乎不是一个真正的解决方案,因为它不会释放线程的资源,我不确定如何在我的代码。


代码

namespace TriviaClient

    public partial class MainWindow : Window
    
        public const int GET_ROOMS_REQUEST = 92;

        public Thread refreshRoomsListThread;

        public static NetworkStream clientStream;

        public MainWindow()
        
            StartClient();
            InitializeComponent();
        

        public void refreshRoomList()
        
            while (true)
            
                this.Dispatcher.Invoke((Action)(() =>
                
                    roomsListErrorBox.Text = "";
                ));
                serializeAndSendMessage(GET_ROOMS_REQUEST, "");
                Dictionary<string, object> response = receiveAndDeserializeMessage();

                if (response.ContainsKey("status") && (string)response["status"] == "1")
                
                    this.Dispatcher.Invoke((Action)(() =>
                    
                        roomsList.Items.Clear();
                    ));

                    Dictionary<int, string> rooms = new Dictionary<int, string>();

                    JArray Jrooms = (JArray)response["rooms"];


                    foreach (JArray room in Jrooms)
                    
                        rooms.Add((int)room[0], (string)room[1]);
                    

                    foreach (KeyValuePair<int, string> roomInDict in rooms)
                    
                        this.Dispatcher.Invoke((Action)(() =>
                        
                            roomsList.Items.Add(new Label().Content = $"roomInDict.Key: roomInDict.Value");
                        ));
                    

                
                else if (response.ContainsKey("message"))
                
                    this.Dispatcher.Invoke((Action)(() =>
                    
                        roomsListErrorBox.Text = (string)response["message"];
                    ));
                
                Thread.Sleep(3000);
            
        
        private void roomListBackButtonClick(object sender, RoutedEventArgs e)
        
            refreshRoomsListThread.Abort();

        
        private void mainMenuJoinRoomButtonClick(object sender, RoutedEventArgs e)
        
            roomsListErrorBox.Text = "";

            refreshRoomsListThread = new Thread(refreshRoomList);
            refreshRoomsListThread.Start();

        
    


【问题讨论】:

while (true) ... Thread.Sleep(3000); - 为什么不使用可以随时停止的计时器?最好是 DispatcherTimer,它还可以消除对 Dispatcher.Invoke 的需求。 Task + CancellationToken 怎么样? -- 你有serializeAndSendMessagereceiveAndDeserializeMessage() 的异步版本还是可以将它们转换为异步(看起来像I/O + JSON 序列化)?您调用的频率如此之高,以至于这里可能只使用 async/await。 Thread.Abort 绝不是一个好主意 - 特别是如果您关心错误和资源丢失。为什么不使用线程池,当您不再需要线程时(如@mm8 所建议),将您的后台工作传递给“好的,立即退出”。您可能还想阅读asyncawait。使用更现代的结构可能有更好的方法来做您想做的事情。 serializeAndSendMessage 方法是否接受 CancellationToken 作为参数? 【参考方案1】:

您应该将DispatcherTimerasync Tick 事件处理程序一起使用,该事件处理程序在Task 中运行长时间运行的操作。

Tick 事件处理程序在 UI 线程中执行,因此您无需使用 Dispatcher。

private readonly DispatcherTimer timer = new DispatcherTimer

    Interval = TimeSpan.FromSeconds(3)
;

public MainWindow()

    StartClient();
    InitializeComponent();

    timer.Tick += OnTimerTick;


private void mainMenuJoinRoomButtonClick(object sender, RoutedEventArgs e)

    timer.Start();


private void roomListBackButtonClick(object sender, RoutedEventArgs e)

    timer.Stop();


private async void OnTimerTick(object sender, EventArgs e)

    roomsListErrorBox.Text = "";

    Dictionary<string, object> response = await Task.Run(
        () =>
        
            serializeAndSendMessage(GET_ROOMS_REQUEST, "");

            return receiveAndDeserializeMessage();
        );


    if (response.ContainsKey("status") && (string)response["status"] == "1")
    
        roomsList.Items.Clear();
        // ...
    

【讨论】:

而且您需要的似乎更多。计时器通常比带有睡眠的循环更合适。【参考方案2】:

您可以设置一个标志以最终退出while 循环,而不是尝试强制中止线程:

private bool _run = true;
public void refreshRoomList()

    while (_run)
    
        ...
    

private void roomListBackButtonClick(object sender, RoutedEventArgs e)

    _run = false;

【讨论】:

你需要Interlocked.ReadInterlocked.Write @Charlieface 为什么需要这些? (这个解决方案在没有这些的情况下有效,现在我正在检查给出的第二个答案,看看它是否也有效) @snatchysquid 因为它可能是内联的,尽管它确实取决于循环内代码的作用 @Charlieface 你能用Interlocked.ReadInterlocked.Read 展示代码的外观吗?我不熟悉这些,不知道如何使用它 @snatchysquid while (Volatile.Read(ref _run))Volatile.Write(ref _run, false);【参考方案3】:

要解决此问题,您可以使用布尔值并让线程处于活动状态,这是一个示例:

        public bool KeepAlive  get; set;  = true;
    private void YourThread()
    


        while (KeepAlive)
        
            //Do your task here
        
    

当你想停止线程时设置 KeepAlive = false;

【讨论】:

以上是关于C# 中止线程:此平台不支持线程中止的主要内容,如果未能解决你的问题,请参考以下文章

java线程暂停,优雅结束并中止

C#错误之 System.Threading.ThreadAbortException:正在中止线程

C# - 线程中止异常(Thread Abort Exception)重新抛出自身

如何中止使用 ThreadPool.QueueUserWorkItem 创建的线程

AppDomain.Unload() 如何中止线程?

中止正在运行长查询的线程