需要帮助将阻塞操作转换为C#/ WPF中的线程操作

时间:2013-05-30 15:16:08

标签: c# wpf multithreading

背景

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

问题

我的问题源于这是一个同步操作的事实,虽然它可以工作,但它只能工作几分钟,并且StopWatch表现不规律并调用多行写入我的SamplesView。这最终导致我的_nextLine计数器超前于实际PRN的位置,并且我得到了一个越​​界异常。

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

由于Samples集合需要每隔一段时间更新一次,我怀疑我需要实现一些后台线程,这个后台线程尊重View在主线程上更新自己的权利给定我读过的内容{{3 }和here。但是,我不知道从哪里开始。

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

//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++;
    }
}

1 个答案:

答案 0 :(得分:1)

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

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
}

您肯定需要改进此示例,以便提高行读取效率。每次文件更改时都不要读取所有行,而是保持读取位置并从最后保留的位置开始读取。