c# thread. join()阻塞主线程

本文关键字:线程 thread join | 更新日期: 2023-09-27 18:17:31

我有3个线程在运行:主线程,readData线程和获取线程。在表单中,当单击play按钮时,它启动设备获取和readData线程。当停止按钮被按下时,我想停止两个线程。然而,acquitionthread . 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();
}

c# thread. join()阻塞主线程

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

相反,你想执行线程取消。MSDN托管线程取消

根据设计,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设置为什么,它都不会终止线程,直到该方法返回。

您应该查看任务并行库和可取消的任务,原始线程对于更高级别的控制正在贬值。也可以考虑async/await,因为你正在阻塞I/O,当你可以等待一个I/O绑定任务时,真的没有理由让一个新线程坐下来等待I/O。