为了账号安全,请及时绑定邮箱和手机立即绑定

线程/进程阻塞未按预期工作

线程/进程阻塞未按预期工作

C#
青春有我 2021-10-09 19:51:31
以下是我的代码结构的高度简化演示:using System.Collections.Concurrent;using System.Threading;using System.Threading.Tasks;namespace ConsoleApp1{    class Program    {        static ConcurrentQueue<string> lst = new ConcurrentQueue<string>();        static bool bExit = false;        static void Main(string[] args)    {        for (int i = 0; i < 1000; i++)        {            lst.Enqueue("LINE " + i.ToString());        }        Thread threadCurrent = Thread.CurrentThread;        Task.Run(() =>        {            // #1 this block causes the process to exit at about line 556 on my PC            //while (threadCurrent.IsAlive)            //{            //  Thread.Sleep(500);            //}            /////            // #2 this block works as expected and exits after line 999 is printed            //while (threadCurrent.IsAlive || lst.Count > 0)            //{            //  Thread.Sleep(500);            //}            /////            // #3 this block never exits the process            threadCurrent.Join();            // forced queue flush would go here            /////            bExit = true;        });        Thread threadWorker = new Thread(new ThreadStart(Worker));        threadWorker.SetApartmentState(ApartmentState.STA);        threadWorker.Start();    }    static private void Worker()    {        while (true)        {            string s;            if (lst.TryDequeue(out s))            {                System.Diagnostics.Debug.WriteLine(s);            }            else            {                Thread.Sleep(100);            }            if (bExit)                break;        }    }}}请参阅 Task.Run() 部分中的 3 个块。在调试器中运行它。使用方法 #1,在我的 PC 上,进程大约在中途退出。#2 按预期工作,#3 永远挂起。这个想法是主线程将消息发布到队列并在工作线程处理它时立即返回。即使主线程已经退出,我也想确保在进程退出之前处理队列中的所有消息。方法 #2 实现了这一点,但我只是不喜欢睡眠,但如果我必须这样做的话,我会忍受它......:)。方法#1证明主线程确实在退出,不是吗?所以我认为方法 #3 会在没有睡眠的情况下完成同样的事情?但是 .Join 永远不会返回,为什么?
查看完整描述

2 回答

?
拉莫斯之舞

TA贡献1820条经验 获得超10个赞

这是一个令人惊讶的行为,但这是可以解释的。


首先,您必须知道进程的生命周期与主线程(又名主线程)的生命周期相关。我以为那是 Windows 的东西,但根据 Raymon Chen 的说法,这是 C 的东西。


无论如何,底线是:.NET 运行时必须保持主线程处于活动状态。


那么当你退出Main程序的方法时会发生什么?按照约定,.NET 运行时必须等待任何未标记为后台的线程。它在RunMainPost方法中这样做,而该方法又会调用WaitForOtherThreads. 这解释了为什么Thread.Join在所有前台线程完成其工作之前调用主线程不会成功。


但是,为什么mainThread.IsAlive返回 false 呢?


首先,您必须知道它IsAlive不直接绑定到线程。相反,它调用ThreadIsRunning. 如果底层线程没有死并且没有用TS_ReportDeador标记TS_Dead(这是因为运行时在线程向托管代码报告死后保持一段时间,以进行一些簿记/整理),这将返回 true 。


回到WaitForOtherThreads,如果您检查代码,您可以看到:


if (!OtherThreadsComplete())

{

    TSLockHolder.Release();


    FastInterlockOr((ULONG *) &pCurThread->m_State, Thread::TS_ReportDead);


    DWORD ret = WAIT_OBJECT_0;

    while (CLREventWaitWithTry(&m_TerminationEvent, INFINITE, TRUE, &ret))

    {

    }

    _ASSERTE(ret == WAIT_OBJECT_0);

}

在m_TerminationEvent由最后一个线程终止设置。但重要的一点是FastInterlockOr((ULONG *) &pCurThread->m_State, Thread::TS_ReportDead);:主线程在等待其他线程之前将自己标记为死。它解释了为什么IsAlive是假的。


我已经通过构建 .NET Core 的自定义版本并注释掉该行来确认该假设。之后,IsAlive报道属实。


为什么要这样做?我只能猜测,但我认为这只是为了使运行时代码更简单。通过将主线程标记为死亡,其他实际死亡的线程可以检查(在上述簿记代码中)是否所有其他线程都死亡,并在这种情况下设置m_TerminationEvent. 这比检查“除了一个线程之外所有其他线程都死了”要稍微优雅一些。不过只是猜测。


查看完整回答
反对 回复 2021-10-09
  • 2 回答
  • 0 关注
  • 197 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信