C#实时读取LabVIEW传感器数据的异步并行实现问题求助
问题分析与解决方案
核心问题拆解
你遇到的两个问题根源分别是:
- async/await方案数据暴增:你在
Start()的while(!stop)循环中反复调用StartReadingThread(),导致同时启动了成百上千个异步读取任务,每个任务都按50ms间隔执行读取,最终数据量远超预期。 - Task.Run方案数据对齐失败:
List<T>并非线程安全集合,后台线程直接操作UI线程维护的列表时,会引发竞态条件,导致数据跳步、重复;同时每次读取整个文件的逻辑,可能会重复读取LabVIEW未写完的内容或同一批数据。
修正后的实现方案
1. 基础准备:定义统一数据类(保证数据对齐)
用一个类封装单组传感器数据,避免多列表操作时出现数据错位:
public class SensorData { public double ForceValue { get; set; } public double TouchValue { get; set; } public DateTime RecordTime { get; set; } // 其他需要的字段 }
2. 线程安全的异步读取实现
private bool _isReading = false; private readonly object _dataLock = new object(); // 用于列表操作的线程同步锁 private long _lastReadPosition = 0; // 记录上次读取到文件的位置,避免重复读取 private string _textFile = "stimulator.txt"; // 启动读取任务(仅需调用一次) private async Task StartReadingAsync() { if (_isReading) return; _isReading = true; try { while (_isReading) { await ReadAndParseSensorDataAsync(); await Task.Delay(50); // 控制读取间隔 } } finally { _isReading = false; } } // 异步读取并解析文件内容 private async Task ReadAndParseSensorDataAsync() { try { if (!File.Exists(_textFile)) return; using var fileStream = new FileStream(_textFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // 如果文件没有新增内容,直接返回 if (fileStream.Length <= _lastReadPosition) return; // 跳转到上次读取的位置,只读取新增内容 fileStream.Seek(_lastReadPosition, SeekOrigin.Begin); using var streamReader = new StreamReader(fileStream); string newContent = await streamReader.ReadToEndAsync(); _lastReadPosition = fileStream.Position; if (string.IsNullOrEmpty(newContent)) return; // 按实际格式解析内容(示例按行分割,需匹配LabVIEW的输出格式) var lines = newContent.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { var dataParts = line.Split(','); // 假设LabVIEW用逗号分隔字段 if (dataParts.Length < 3) continue; // 解析数据 double.TryParse(dataParts[0].Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out double force); double.TryParse(dataParts[1].Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out double touch); DateTime.TryParse(dataParts[2], out DateTime recordTime); var sensorData = new SensorData { ForceValue = Math.Round(force, 2), TouchValue = touch, RecordTime = recordTime }; // 加锁保证多线程下数据操作的原子性,避免错位 lock (_dataLock) { indexForceValues.Add(sensorData.ForceValue); indextouches.Add(sensorData.TouchValue); timesPain.Add(sensorData.RecordTime); } // 如果需要更新UI,必须切换回UI线程(WPF示例) Application.Current.Dispatcher.Invoke(() => { // 更新UI控件或绑定的集合 }); } } catch (IOException ex) { Console.WriteLine($"文件读取错误:{ex.Message}"); } } // 停止读取 private void StopReading() { _isReading = false; }
3. 正确的调用方式
在GUI事件中启动读取任务(仅调用一次):
private async void btnStartReading_Click(object sender, RoutedEventArgs e) { await StartReadingAsync(); } private void btnStopReading_Click(object sender, RoutedEventArgs e) { StopReading(); }
关键优化点说明
- 避免重复启动任务:通过
_isReading标志严格控制读取任务仅启动一次,彻底解决多任务并发读取导致的数据暴增问题。 - 线程安全操作:用
lock保证列表操作的原子性,确保多组数据始终对齐;如果是.NET Core/.NET 5+,也可以用ConcurrentQueue<SensorData>替代List<T>加锁的方式。 - 增量读取文件:记录上次读取的文件位置,只读取LabVIEW新增的内容,避免重复解析同一批数据。
- UI线程同步:所有UI相关操作必须通过
Dispatcher.Invoke切换回UI线程执行,避免跨线程操作异常。
内容的提问来源于stack exchange,提问作者user30206843
相关产品推荐
相关产品推荐

