2

背景

有一个我无法控制的外部程序每 8 秒将制表符分隔的行写入“PRN”文件 (Test_1234.prn)。我的程序的工作是读取该文件并将第 n 行(写入文件的最后一行)写入一个简单的示例 ListView。我还在视图中运行了一个秒表,我计划使用时钟通过检查秒表的秒数来驱动 PRN 的读数。每 4 秒我会出去读取文件并返回一个 DataTable,然后每 8 秒我会将该 DataTable 的第 n 行写入我的视图。PRN 确实有很多行,但大小永远不会超过 1mb - 5mb。

问题

我的问题源于这是一个同步操作,虽然它工作,但它只工作几分钟,并且 StopWatch 行为不规律,并调用向我的 SamplesView 写入多行。这最终导致我的 _nextLine 计数器领先于实际 PRN 所在的位置,并且我得到一个越界异常。

鉴于缺乏线程经验,我不知道从哪里开始使用线程来修复此代码,以便它在它应该做的时间间隔内做它应该做的事情。

由于 Samples 集合需要定期更新,我怀疑我需要实现一些东西,例如尊重视图权利的后台线程,以便根据我在此处此处阅读的内容在主线程上更新自身。但是,再一次,我不知道从哪里开始。

有人可以帮助实施线程解决方案来解决我的特定问题吗?

//Xaml belongs to my CalibrationView.xaml
<StackPanel Orientation="Vertical">
    <Label Content="Run Time:" FontSize="16" FontWeight="Bold" Margin="10,0,0,0"/>
    <TextBlock Name="ClockTextBlock" Text="{Binding CurrentTime, Mode=TwoWay}"/>
    <ListView ItemsSource="{Binding Samples}" SelectedItem="{Binding SelectedSample}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Time" Width="70" DisplayMemberBinding="{Binding Time}"/>
            </GridView>
        </ListView.View>
    </ListView>
</StackPanel>

public class CalibrationViewModel : ViewModelBase
{
    DispatcherTimer dt = new DispatcherTimer();
    Stopwatch stopWatch = new Stopwatch();

    private bool _firstTime = true;
    private int _nextLine = 1;
    private int _sampleCount;

    public CalibrationViewModel(Calibration calibration)
    {
        Samples = new ObservableCollection<StepRecord>();

        dt.Tick += dt_Tick;
        dt.Interval = new TimeSpan(0, 0, 0, 1);
    }

    public ObservableCollection<StepRecord> Samples { get; set; }
    public DataTable Prn { get; set; }

    public String CurrentTime
    {
        get { return _currentTime; }
        set
        {
            if (_currentTime != value)
            {
                _currentTime = value;
                OnPropertyChanged("CurrentTime");
            }
        }
    }

    void dt_Tick(object sender, EventArgs e)
    {
        if (stopWatch.IsRunning)
        {
            TimeSpan ts = stopWatch.Elapsed;

            CurrentTime = String.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds);

            if (ts.Seconds % 4 == 0)
            {
                // Custom parser that reads a Tab Delimited file and returns a DataTable (PRN)
                PrnParser parser = new PrnParser(); 

                Prn = parser.PopulateDataTableFromTextFile(@"C:\Users\Path\To\File\Test_1234.prn");

                if (_firstTime)
                {
                    _nextLine = Prn.Rows.Count - 1;
                    _firstTime = false;
                }
            }

            if (ts.Seconds % 8 == 0)
            {
                WriteLineToSamplesCollection();
            }
        }
    }

    private void WriteLineToSamplesCollection()
    {
        var record = new StepRecord
        {
            Time = (Prn.Rows[NextLine].Field<String>("Column-2")),
        };

        CurrentSample = record;

        Samples.Insert(0, record);

        _nextLine++;

        _sampleCount++;
    }
}
4

1 回答 1

2

以下代码显示了一个非常基本的示例,说明如何使用 FileSystemWatcher 监视特定文本文件的更改并在文件更改时读取最后一行。由于该Changed事件已在单独的线程(来自 ThreadPool)中调用,因此您不需要关心异步,除非您需要Dispatcher在更新 UI 时调用。

private FileSystemWatcher fsw;

public MainWindow()
{
    InitializeComponent();

    fsw = new FileSystemWatcher
    {
        Path = @"C:\Users\Path\To\File",
        Filter = "Test_1234.prn",
        NotifyFilter = NotifyFilters.LastWrite
    };

    fsw.Changed += (o, e) =>
        {
            var lastLine = File.ReadAllLines(e.FullPath).Last();
            Dispatcher.BeginInvoke((Action<string>)HandleChanges, lastLine);
        };

    fsw.EnableRaisingEvents = true;
}

private void HandleChanges(string lastLine)
{
    // update UI here
}

您当然需要改进这个示例,以使行阅读更有效。不要在每次文件更改时都读取所有行,而是保持读取位置并从最后保存的位置开始读取。

于 2013-05-30T16:42:08.843 回答