C# 控制台应用程序没有';不要听话。加入

C# 控制台应用程序没有';不要听话。加入,c#,multithreading,C#,Multithreading,我很难理解为什么我的控制台应用程序不等到它产生的线程完全终止。我认为这与以下事实有关:所讨论的线程也会产生自己的子线程和/或包含System.Timer 基本程序流程如下所示。Main针对Simulator.Start方法创建一个新线程,然后加入,直到该线程终止。Simulator.Start创建一个新的计时器(以限制它应该执行多长时间),然后创建/运行一组子线程。当计时器引发已过事件时,这表示模拟器应终止其所有子线程并生成报告。问题是,一旦所有子线程终止,控制台应用程序就会退出,并且生成报告的

我很难理解为什么我的控制台应用程序不等到它产生的线程完全终止。我认为这与以下事实有关:所讨论的线程也会产生自己的子线程和/或包含System.Timer

基本程序流程如下所示。Main针对Simulator.Start方法创建一个新线程,然后加入,直到该线程终止。Simulator.Start创建一个新的
计时器
(以限制它应该执行多长时间),然后创建/运行一组子线程。当
计时器
引发
已过
事件时,这表示模拟器应终止其所有子线程并生成报告。问题是,一旦所有子线程终止,控制台应用程序就会退出,并且生成报告的代码永远不会执行(请参阅下面的Simulator.Stop方法)

希望一些伪代码能有所帮助:

public class Program
{
    private static Simulator _simulator;

    private static void Main(string[] args)
    {
        var options = new SimulationOptions();
        //check for valid options
        if (!Parser.Default.ParseArguments(args, options)) return;

        _simulator = new Simulator(options);

        var thread = new Thread(_simulator.Start) {IsBackground = false};
        thread.Start();
        thread.Join();
    }
}

public class Simulator
{
    private readonly SimulationOptions _options;
    private readonly List<Thread> _threads = new List<Thread>();
    private readonly List<Worker> _workers = new List<Worker>();
    private static Timer _timer;

    public Simulator(SimulationOptions options)
    {
        _options = options;
        StartTimer(_options.LengthOfTest);
    }

    private void StartTimer(int lengthOfTest)
    {
        _timer = new Timer {Interval = lengthOfTest*1000};
        _timer.Elapsed += Timer_Elapsed;
        _timer.Start();
    }

    private void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        _timer.Stop();
        Stop();
    }

    public void Stop()
    {
        // Request that the worker thread stop itself:
        foreach (Worker worker in _workers)
        {
            worker.RequestStop();
        }

        GenerateReport(); //<-- this code never gets executed
    }

    private XDocument GenerateReport()
    {
        //build an awesome report
    }

    public void Start()
    {
        _threads.Clear();
        _workers.Clear();
        for (int i = 0; i < _options.NumberOfClients; i++)
        {
            _workers.Add(new Worker());
            _threads.Add(new Thread(_workers.Last().PumpMessages));
            _threads.Last().Start();
        }
    }
}

public class Worker
{
    private bool _shouldStop = false;

    public void PumpMessages()
    {
        while (!_shouldStop)
        {
            //does cool stuff until told to stop
        }
    }

    public void RequestStop()
    {
        _shouldStop = true;
    }
}
公共类程序
{
专用静态仿真器(u仿真器),;
私有静态void Main(字符串[]args)
{
var options=新的模拟选项();
//检查有效选项
if(!Parser.Default.ParseArguments(args,options))返回;
_模拟器=新的模拟器(选项);
var thread=new thread(_simulator.Start){IsBackground=false};
thread.Start();
thread.Join();
}
}
公共类模拟器
{
专用只读模拟选项_选项;
私有只读列表_threads=new List();
私有只读列表_workers=new List();
专用静态定时器_定时器;
公共模拟器(模拟选项)
{
_选项=选项;
起始点(_options.LengthOfTest);
}
专用void起始点(int lengthOfTest)
{
_计时器=新计时器{间隔=最长长度*1000};
_timer.appeated+=timer\u appeated;
_timer.Start();
}
私有无效计时器\u已过(对象发送器,ElapsedEventArgs e)
{
_timer.Stop();
停止();
}
公共停车场()
{
//请求工作线程停止自身:
foreach(工人中的工人)
{
worker.RequestStop();
}

GenerateReport();//开始方法中的任何内容都不会使线程保持活动状态。当以下方法完成时,线程也会保持活动状态。然后调用thread.Join,这就是结束

public void Start()
{
    _threads.Clear();
    _workers.Clear();
    for (int i = 0; i < _options.NumberOfClients; i++)
    {
        _workers.Add(new Worker());
        _threads.Add(new Thread(_workers.Last().PumpMessages));
        _threads.Last().Start();
    }
}
public void Start()
{
_线程。清除();
_工人。清除();
对于(int i=0;i<\u options.NumberOfClients;i++)
{
_workers.Add(新Worker());
_添加(新线程(_workers.Last().PumpMessages));
_threads.Last().Start();
}
}

如果您打算等待这项工作完成,请考虑在您使用的每个工作线程上等待MalualReSeEvices。

您的方法应该如下所示

public void Start()
{
    _threads.Clear();
    _workers.Clear();
    var evts = new List<ManualResetEvent>()
    for (int i = 0; i < _options.NumberOfClients; i++)
    {
        ManualResetEvent evt = new ManualResetEvent(false);
        evts.Add(evt);
        _workers.Add(new Worker(evt));
        _threads.Add(new Thread(_workers.Last().PumpMessages));
        _threads.Last().Start();
    }
    WaitHandle.WaitAll(evts.ToArray());
}

public class Worker
{
    private bool _shouldStop = false;
    private readonly ManualResetEvent @event;

    public Worker(ManualResetEvent @event)
    {
        this.@event = @event;
    }
    public void PumpMessages()
    {
        while (!_shouldStop)
        {
            //does cool stuff until told to stop
        }
        @event.Set();
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
}
public void Start()
{
_线程。清除();
_工人。清除();
var evts=新列表()
对于(int i=0;i<\u options.NumberOfClients;i++)
{
ManualResetEvent evt=新的ManualResetEvent(错误);
evt.Add(evt);
_工人。添加(新工人(evt));
_添加(新线程(_workers.Last().PumpMessages));
_threads.Last().Start();
}
WaitHandle.WaitAll(evts.ToArray());
}
公社工人
{
private bool_shouldStop=false;
私有只读手册重置事件@事件;
公共工作者(ManualResetEvent@event)
{
此@event=@event;
}
公开作废PumpMessages()
{
而(!\u应该停止)
{
//做一些很酷的事情直到被告知停止
}
@event.Set();
}
公共void RequestStop()
{
_shouldStop=true;
}
}

Join方法只等待您加入的线程实例,因此
Simulator.Start
只创建一些线程并终止,结果
Join
返回,您的主线程终止。但您的应用程序仍然处于活动状态(原因是其他一些前台线程仍在运行)

进程将在所有
前台线程
终止时终止。因此,当您在循环中调用
RequestStop
时,只要您的子线程从
PumpMessages
方法返回,所有前台线程都将终止

public void Stop()
{
    // Request that the worker thread stop itself:
    foreach (Worker worker in _workers)
    {
        worker.RequestStop();
    }
    <--here all foreground threads are ready to terminate
    GenerateReport(); //<-- this code never gets executed
}

当然,它通过Start()方法运行,最多需要几微秒。首先,在工作线程上调用Start()没有任何意义。您需要Join()所有这些线程都是由您启动的。问题不在于start方法。控制台应用程序挂起的时间足够长,可以触发Stop方法的Timer_appeased事件。但是,执行会在该方法的中途结束。或者更好的做法是使用tasks。是的,或者更好的做法是,将Task并行库与async/await一起使用。但是,如果您只是学习线程,这是一个很好的练习。哦,当然。在跳转到快捷方式之前,了解一些底层技术的陷阱总是有帮助的。我比我更喜欢你的答案,但我认为你对停止的解释并不彻底。这并不是说“所有前景线程都会死”。这是一个竞争条件,因为Stop是从计时器线程调用的,这里没有任何东西来协调Stop方法与主执行。尽管如此,您的t.Join解决方案仍然是一个方向。@Phillips Cottgivens我正要离开,只是匆匆回答,让我修改它。我在Start方法中添加了Join语句d、 但这没有什么区别。只是想确保您知道控制台线程在停止方法执行之前一直挂起……但它在GenerateReports之前挂起。您能在停止childs之前调用
GenerateReport
吗?如果有帮助的话
public void Stop()
{
    // Request that the worker thread stop itself:
    foreach (Worker worker in _workers)
    {
        worker.RequestStop();
    }
    <--here all foreground threads are ready to terminate
    GenerateReport(); //<-- this code never gets executed
}
public void Start()
{
    _threads.Clear();
    _workers.Clear();
    for (int i = 0; i < _options.NumberOfClients; i++)
    {
        _workers.Add(new Worker());
        _threads.Add(new Thread(_workers.Last().PumpMessages));
        _threads.Last().Start();
    }
    foreach (var t in _threads)
       t.Join();
}