wpf 为什么我的计时器不能动态显示文件大小?

wqlqzqxt  于 2022-11-18  发布在  其他
关注(0)|答案(2)|浏览(133)

所以,基本上,我正在制作一个程序,其中25个任务将相同的随机任务写入一个文件,直到它达到一定的大小,并且我希望以0.5秒的间隔动态显示所选文件的文件大小,所以我制作了一个与Timer_Elapsed挂钩的计时器,它应该每0.5秒执行一次,并在UI上显示,我在mainWindow.xaml的文本块x:Name=“fileSize”中指定了这个UI,所以我将createTimer函数放在mainwindow.xaml.cs中的btnGo_Click函数中,这样事件将提取selectedFile的正确fileInfo。如果有任何错误的建议,我将不胜感激。我还在需要的情况下共享FileIO

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows;
using System.Threading;

namespace WriteFile
{
    internal class FileIO
    {
        private object _lock = new object();
        public volatile bool sizeReached = false;
        private StreamWriter sw = null;
        Mutex mut = null;

        public FileIO()
        {
            if (!Mutex.TryOpenExisting("MyMutex", out mut))
            {
                mut = new Mutex(true, "MyMutex");
                mut.ReleaseMutex();
            }
        }

        internal void WriteFile(string FilePath)
        {
            while (!sizeReached)
            {
                mut.WaitOne();
                    try
                    {
                        using (sw = new StreamWriter(FilePath, true))
                        {
                            sw.WriteLine(Guid.NewGuid());
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Exception: " + ex.Message);
                    }

                    finally
                    {
                        if (sw != null)
                        {
                            sw.Close();
                        }
                    }
                mut.ReleaseMutex();              
            }
        }

        internal void SizeMonitor(string FPath, int MaxSize, Task[] tasks)
        {
            FileInfo fi = null;
            while (!sizeReached)
            {
                if (File.Exists(FPath))
                {
                    fi = new FileInfo(FPath);

                    if (fi.Length >= MaxSize)
                    {
                        sizeReached = true;
                    }
                }

                if (sizeReached)
                {
                    foreach (Task task in tasks)
                    {
                        task.Wait();
                    }
                }
                Thread.Sleep(1);
            }

            MessageBox.Show(fi.Length.ToString());
            MessageBox.Show("Done");
        }
    }

}

mainWindow.xaml

<Window x:Class="WriteFile.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WriteFile"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock x:Name="fileSize"/>
        <TextBox Name ="TargetSize"  VerticalAlignment="Center" FontSize="20">
        </TextBox>
        <Label Content="Target Size" HorizontalAlignment="Left" Margin="92,150,0,0" VerticalAlignment="Top"/>
        <Button Name ="btnGo" Content="Write to file" HorizontalAlignment="Left" Margin="92,267,0,0" VerticalAlignment="Top" Width="100" Click="btnGo_Click"/>
    </Grid>
</Window>

mainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using Microsoft.Win32;
using System.ComponentModel;
using System.Timers;
using System.Threading;
using System.Runtime.CompilerServices;

namespace WriteFile
{
    public partial class MainWindow : Window
    {
        System.Timers.Timer timer = new System.Timers.Timer();
        Task[] tasks;
        Task MonitorTask;
        static FileIO fio = new FileIO();
        static string fPath;
        static FileInfo fileInfo;

        public MainWindow()
        {
            InitializeComponent();
            CreateTimer();
        }

        public void CreateTimer()
        {
            var timer = new System.Timers.Timer(500); // fire every 0.5 second
            timer.Enabled = true;
            timer.Elapsed += Timer_Elapsed;
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            fileSize.Text = fileInfo.Length.ToString();
        }

        private void btnGo_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.ShowDialog();
            Stream myStream;
            saveFileDialog.FilterIndex = 2;
            saveFileDialog.RestoreDirectory = true;

            if (File.Exists(saveFileDialog.FileName))
            {
                File.Delete(saveFileDialog.FileName);
            }

            if ((myStream = saveFileDialog.OpenFile()) != null)
            {
                StreamWriter sw = new StreamWriter(myStream);
                sw.Write(" your text");
                myStream.Close();
            }
            
            int NoOfTasks = 25;
            int Target = Convert.ToInt32(TargetSize.Text);

            fPath = saveFileDialog.FileName;
            tasks = new Task[NoOfTasks];

            fio.sizeReached = false;
            fileInfo = new FileInfo(fPath);

            for (int i = 0; i < NoOfTasks; i++) 
            {
                tasks[i] = new Task(() => fio.WriteFile(fPath));
                tasks[i].Start();
            }

            MonitorTask = new Task(() => fio.SizeMonitor(fPath, Target, tasks));
            MonitorTask.Start();
        }
    }
}
rryofs0p

rryofs0p1#

正如其他人所提到的,FileInfo.Length值是预先缓存的。如果关联的文件已经更改,FileInfo需要刷新那些缓存的值。要完成此操作,您必须显式调用FileInfo.Refresh方法。FileInfo不监视关联的文件。
此外,System.Threading.Timer在后台线程上执行回调。在WPF中,您只能从调度程序线程(UI线程)引用DispatcherObject(例如TextBlock)。因此,您应该使用DispatcherTimerDispatcherTimer在调度程序线程上执行回调。
此外,您可能希望在写入文件完成后停止计时器。
您不应该使用Task.Start。除了Task.StartMutex之外,您只应该使用await来取代Task。在您的内容中,您可以使用Task.WhenALlawaitTask对象的集合。
您应该使用StreamWriterasync API,而不是显式创建Task
您实际上没有并发代码(您也不会从中受益):volatile字段的Mutex、声明和锁定对象是冗余的。此外,由于您已经使用using块来处理示例的生存期,因此不需要在finally块中显式关闭StreamWriter
您的代码中还有一些缺陷(例如一些逻辑问题)。我已经重构了您的版本以消除一些问题,例如通过使用异步StreamWriter API和async/await。
因为很难理解你的代码(由于缺少上下文),所以不可能进一步改进它。例如,创建一个连接字符串(例如由25个值组成)将导致一个文件写入(和资源分配),这将显著提高整体性能。

文件IO.cs

namespace WriteFile
{
    internal class FileIO
    {
        public bool IsSizeReached { get; private set; }

        public FileIO()
        {
        }

        internal async Task WriteToFileAsync(string filePath)
        {
            if (this.IsSizeReached)
            {
                 return;
            }

            using (var sw = new StreamWriter(filePath, true))
            {
                await sw.WriteLineAsync(Guid.NewGuid());
            }   
        }

        internal async Task SizeMonitorAsync(string fPath, int maxSize, IEnumerable<Task> tasks)
        {
            
            FileInfo fi = new FileInfo(fPath);
            this.IsSizeReached = fi.Length >= maxSize;
            if (this.IsSizeReached)
            {
                return;
            }

            await Task.WhenAll(tasks);            

            MessageBox.Show(fi.Length.ToString());
            MessageBox.Show("Done");
        }
    }
}

主窗口.xaml.cs

namespace WriteFile
{
    public partial class MainWindow : Window
    {
        private DispatcherTimer Timer { get; }
        private FileIO Fio { get; }
        private FileInfo DestinationFileInfo { get; }

        public MainWindow()
        {
            InitializeComponent();
            this.Fio = new FileIO();
        }

        public void StartTimer()
        {
            this.Timer = new DispatcherTimer(
              TimeSpan.FromMilliseconds(500), 
              DispatcherPriority.Background, 
              OnTimerElapsed,
              this.Dispatcher);
            this.Timer.Start();
        }

        public void StopTimer() => this.Timer.Stop();

        private void OnTimerElapsed(object sender, EventArgs e)
        {
            this.fileSize.Text = this.DestinationFileInfo.Length.ToString();
        }

        private async void btnGo_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.ShowDialog();
            saveFileDialog.FilterIndex = 2;
            saveFileDialog.RestoreDirectory = true;

            if (File.Exists(saveFileDialog.FileName))
            {
                File.Delete(saveFileDialog.FileName);
            }

            Stream myStream;
            if ((myStream = saveFileDialog.OpenFile()) != null)
            {
                // Dispose StreamWriter and underlying Stream
                using (var sw = new StreamWriter(myStream))
                {
                  await sw.WriteAsync(" your text");
                }
            }
            
            int noOfTasks = 25;

            // TODO::You must validate the input to prevent invalid or unreasonable values.
            // Currently, this line will throw and crash the application if the input is invalid (not numeric).
            int target = Convert.ToInt32(TargetSize.Text);

            string fPath = saveFileDialog.FileName;
            var tasks = new List<Task>();
            this.DestinationFileInfo = new FileInfo(fPath);

            StartTimer();
            for (int i = 0; i < noOfTasks; i++) 
            {
                Task writeToFileTask = this.Fio.WriteToFileAsync(fPath);
                tasks.Add(writeToFileTask);
            }

            await this.Fio.SizeMonitorAsync(fPath, target, tasks));
            StopTimer();
        }
    }
}
kyks70gy

kyks70gy2#

计时器。计时器异步工作,如果未设置SynchronizingObject,它将在池线程上引发Elapsed事件。使用WPF UI元素只能在其Dispatcher的线程上完成(几乎总是应用程序的主UI线程)。
从众多可能的解决方案中选择两个:

private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            // Asynchronously getting the right data
            string text = fileInfo.Length.ToString();

            // Using the Window's Dispatcher
            // to setting ready-made data to UI elements.
            Dispatcher.BeginInvoke(() => fileSize.Text = text);
        }

第二个选项是不使用计时器:

public MainWindow()
        {
            Initialized += RefreshAsync;
            InitializeComponent();
        }
        public async void RefreshAsync(object? sender, EventArgs e)
        {
            while(true)
            {
                string text = await Task.Run(() => fileInfo.Length.ToString());
                fileSize.Text = text;
                await Task.Delay(500);
            }
        }

相关问题