我有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();
}
答案 0 :(得分:2)
您不想阻止并等待其他线程完成。这就是Thread.Join()的作用。
相反,您可能希望执行线程取消。 MSDN Managed Thread Cancellation
答案 1 :(得分:2)
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.