C#Thread.Join()阻塞主线程

时间:2015-10-12 17:30:38

标签: c# multithreading

我有3个线程在运行:主线程,readData线程和获取线程。从表单中,单击播放按钮时,它将启动设备获取和readData线程。当按下停止按钮时,我想要停止两个线程。但是,acqusitionThread.Join()会阻止执行。我做错了什么?

主要表格

 private void btnPlay_Click(object sender, EventArgs e)
        {
          daqObj.Start();
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
          daqObj.Stop();
        }

数据采集类,用于从设备读取数据

     public void Start()
            {
                _isRunning = true;

            acquisitionDevice.StartAcquisition(); //starts thread for acquisition

                //start data acquisition thread
                _readDataThread = new Thread(readData);
                _readThread.Name = "Read data Thread";
                _redThread.Priority = ThreadPriority.AboveNormal;
                _readThread.Start();
            }

       public void ReadData()
        {

            try
            {
                // write data to file
                while (_isRunning)
                {
                    //Reads data (dequeues from buffer)
                    float[] data = acquisitionDevice.ReadData(numValuesAtOnce);

                    //Do other stuff with data (eg: save to file)
                }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("\t{0}", ex.Message);
                }
    }


            public void Stop()
            {
                _isRunning = false;

                    if ((_writeToFileThread != null) && _writeToFileThread.IsAlive)
                        _writeToFileThread.Join(); //stop readData thread

                    acquisitionDevice.StopAcquisition(); //stops acquisition thread

                     Console.WriteLine("acquisiton thread stopped); //THIS IS NEVER EXECUTED

            }

设备采购类:

 public void StartAcquisition(Dictionary<string, DeviceConfiguration> deviceSerials)
        {
            //ensure that data acquisition is not already running
            if (_isRunning || (_acquisitionThread != null && _acquisitionThread.IsAlive))
                throw new InvalidOperationException("Data acquisition is already running!");

            _isRunning = true;

            //initialize buffer
            _buffer = new WindowedBuffer<float>(BufferSizeSeconds * sampleRate * totalChannels);

            //start data acquisition thread
            _acquisitionThread = new Thread(DoAcquisition);
            _acquisitionThread.Name = "DataAcquisition Thread";
            _acquisitionThread.Priority = ThreadPriority.Highest;
            _acquisitionThread.Start(deviceSerials);
        }

 public void StopAcquisition()
        {
            //tell the data acquisition thread to stop
            _isRunning = false;

            //wait until the thread has stopped data acquisition
            if (_acquisitionThread != null)
                _acquisitionThread.Join(); //THIS BLOCKS

            Console.WriteLine("ended"); //THIS IS NEVER EXECUTED
        }

修改 我没有使用单独的线程来读取数据,而是在令牌取消中进行。我使用一个单独的线程从设备中获取数据(这是连续获取数据所需的),然后我读取它并将其写入带有令牌取消的文件。这是有效的代码:

public void StartAcquisition()
            {
                // Initialize token
                _cancellationTokenSourceObj = new CancellationTokenSource();
                var token = _cancellationTokenSourceObj.Token;

                    Task.Factory.StartNew(() =>
               {

                   // Start acquisition
                   try
                   {
                       // Write device configuration parameters to .txt file
                       System.IO.StreamWriter file = new System.IO.StreamWriter(deviceConfFilePath);
                       file.WriteLine(gUSBampObj.GetDeviceConfigurationString());
                       file.Close();

                       // create file stream
                       using (_fileStream = new FileStream(daqFilePath, FileMode.Create))
                       {
                           using (BinaryWriter writer = new BinaryWriter(_fileStream))
                           {
                               // start acquisition thread
                               deviceAcquisition.StartAcquisition();

                               // write data to file
                               while (!token.IsCancellationRequested)
                               {
                                   float[] data = deviceAcquisition.ReadData(numValuesAtOnce);

                                   // write data to file
                                   for (int i = 0; i < data.Length; i++)
                                       writer.Write(data[i]);
                               }
                           }
                       }
                   }
                   catch (Exception ex)
                   {
                       Console.WriteLine("\t{0}", ex.Message);
                   }

               }, token)
           .ContinueWith(t =>
           {
               //This will run after stopping, close files and devices here

               // stop data acquisition
               deviceAcquisition.StopAcquisition();

           });


                }
            }

public void StopAcquisition()
{
    _cancellationTokenSourceObj.Cancel();
}

2 个答案:

答案 0 :(得分:2)

您不想阻止并等待其他线程完成。这就是Thread.Join()的作用。

相反,您可能希望执行线程取消。 MSDN Managed Thread Cancellation

答案 1 :(得分:2)

根据设计,{p> Thread.Join()来自MSDN

的阻止来电
  

Join是一种同步方法,阻塞调用线程(即调用方法的线程),直到调用了Join方法的线程完成为止。使用此方法可确保线程已终止。 如果线程没有终止,调用者将无限期地阻止。

(强调我的)

所以这是设计的,因为你没有用超时调用其中一个重载。但是,您的代码有另一个问题,您可能没有像您想的那样发信号通知终止线程。

如果volatile关键字出现在哪里,您应该使用它声明isRunning字段,如下所示:

private volatile bool _isRunning;

这将确保编译器不对字段值使用单线程缓存优化,并在每次读取该字段时获取最新值。由于您要从多个线程更新该字段,因此需要将其标记为volatile

您遇到的另一个问题是while循环:

while (_isRunning)
{
    //Reads data (dequeues from buffer)
    float[] data = acquisitionDevice.ReadData(numValuesAtOnce);

    //Do other stuff with data (eg: save to file)
}

问题在于这一行:

float[] data = acquisitionDevice.ReadData(numValuesAtOnce);

如果这是一个阻塞调用,并且ReadData没有返回,无论你将_isRunning设置为什么,它都不会终止该线程,直到该方法返回。

您应该查看Task Parallel Library和可取消的任务,原始线程正在折旧以获得更高级别的控制权。另外考虑async/await,因为你正在阻塞I / O,当你可以等待I / O绑定任务时,没有理由让新线程等待I / O.