You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#实时读取LabVIEW传感器数据的异步并行实现问题求助

问题分析与解决方案

核心问题拆解

你遇到的两个问题根源分别是:

  1. async/await方案数据暴增:你在Start()的while(!stop)循环中反复调用StartReadingThread(),导致同时启动了成百上千个异步读取任务,每个任务都按50ms间隔执行读取,最终数据量远超预期。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:42:15