简体   繁体   English

从后台线程问题更新 UI

[英]Updating UI from background thread issues

I'm coding a class to move and copy files.我正在编写 class 来移动和复制文件。 I'm raising events when the current file progress and the total progress changes.当当前文件进度和总进度发生变化时,我会引发事件。 When I test the code on my XP machine, it works fine, but when I run it on my Windows 7 64-Bit machine, the current progress doesn't update the UI correctly.当我在我的 XP 机器上测试代码时,它工作正常,但是当我在我的 Windows 7 64 位机器上运行它时,当前进度无法正确更新 UI。 The current progress ProgressBar only gets half way then starts on the next file which does the same.当前进度 ProgressBar 仅获得一半,然后从下一个执行相同操作的文件开始。 The total progress ProgressBar updates fine.总进度 ProgressBar 更新正常。 Any ideas why this is happening?任何想法为什么会发生这种情况?

EDIT: The Windows 7 machine is running a quad-core and the XP is running a dual-core.编辑:Windows 7 机器运行四核,XP 运行双核。 Not sure if that might be what's making a difference.不确定这是否会有所作为。 I'm only a hobbyist so excuse my ignorance:)我只是一个业余爱好者,所以请原谅我的无知:)

EDIT: Code added (Background)编辑:添加代码(背景)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Timers;
using Timer = System.Timers.Timer;

namespace nGenSolutions.IO
{
public class FileTransporter
{
    #region Delegates

    public delegate void CurrentFileChangedEventHandler(string fileName);

    public delegate void CurrentProgressChangedEventHandler(int      percentComplete);

    public delegate void CurrentWriteSpeedUpdatedEventHandler(long bytesPerSecond);

    public delegate void TotalProgressChangedEventHandler(int percentComplete);

    public delegate void TransportCompleteEventHandler(FileTransportResult result);

    #endregion

    private readonly List<string> _destinationFiles = new List<string>();
    private readonly List<string> _sourceFiles = new List<string>();

    private long _bytesCopiedSinceInterval;
    private FileTransportResult _result;

    private Timer _speedTimer;
    private long _totalDataLength;

    private BackgroundWorker _worker;

    public bool TransportInProgress { get; private set; }

    public event CurrentFileChangedEventHandler CurrentFileChanged;

    public event CurrentProgressChangedEventHandler CurrentProgressChanged;

    public event CurrentWriteSpeedUpdatedEventHandler CurrentWriteSpeedUpdated;

    public event TotalProgressChangedEventHandler TotalProgressChanged;

    public event TransportCompleteEventHandler TransportComplete;

    public void AddFile(string sourceFile, string destinationFile)
    {
        if (!File.Exists(sourceFile))
            throw new FileNotFoundException("The specified file does not exist!", sourceFile);

        var fileInfo = new FileInfo(sourceFile);

        _totalDataLength += fileInfo.Length;

        _sourceFiles.Add(sourceFile);
        _destinationFiles.Add(destinationFile);
    }

    public void BeginTransport()
    {
        // update the write speed every 3 seconds
        _speedTimer = new Timer {Interval = 3000};
        _speedTimer.Elapsed += SpeedTimerElapsed;

        _worker = new BackgroundWorker();
        _worker.DoWork += DoTransport;
        _worker.RunWorkerCompleted += WorkerCompleted;

        _worker.RunWorkerAsync();
        _speedTimer.Start();

        TransportInProgress = true;
    }

    private void SpeedTimerElapsed(object sender, ElapsedEventArgs e)
    {
        InvokeCurrentSpeedUpdated(_bytesCopiedSinceInterval);

        _bytesCopiedSinceInterval = 0;
    }

    private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        TransportInProgress = false;
        InvokeTransportComplete(_result);
    }

    public void CancelTransport(bool rollbackChanges)
    {
        if (TransportInProgress == false)
            throw new InvalidOperationException("You tried to stop the transport before you started it!");

        _result = FileTransportResult.Cancelled;

        _worker.CancelAsync();

        while (_worker.IsBusy)
        {
            // wait for worker to die an 'orrible death
        }

        // TODO: rollback changes if requested
    }

    private void DoTransport(object sender, DoWorkEventArgs e)
    {
        long totalBytesCopied = 0;
        int totalPercentComplete = 0;

        for (int i = 0; i < _sourceFiles.Count; i++)
        {
            string sourceFile = _sourceFiles[i];
            string destinationFile = _destinationFiles[i];

            long currentFileLength = new FileInfo(sourceFile).Length;

            InvokeCurrentFileChanged(sourceFile);

            using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
            {
                using (var destinationStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write))
                {
                    using (var reader = new BinaryReader(sourceStream))
                    {
                        using (var writer = new BinaryWriter(destinationStream))
                        {
                            int lastPercentComplete = 0;

                            for (int j = 0; j < currentFileLength; j++)
                            {
                                writer.Write(reader.ReadByte());

                                totalBytesCopied += 1;
                                _bytesCopiedSinceInterval += 1;

                                int current = Convert.ToInt32((j/(double) currentFileLength)*100);
                                int total = Convert.ToInt32((totalBytesCopied/(double) _totalDataLength)*100);

                                // raise progress events every 3%
                                if (current%3 == 0)
                                {
                                    // only raise the event if the progress has increased
                                    if (current > lastPercentComplete)
                                    {
                                        lastPercentComplete = current;
                                        InvokeCurrentProgressChanged(lastPercentComplete);
                                    }
                                }

                                if (total%3 == 0)
                                {
                                    // only raise the event if the progress has increased
                                    if (total > totalPercentComplete)
                                    {
                                        totalPercentComplete = total;
                                        InvokeTotalProgressChanged(totalPercentComplete);
                                    }
                                }
                            }
                        }

                        InvokeCurrentProgressChanged(100);
                    }
                }
            }
        }

        InvokeTotalProgressChanged(100);
    }

    private void InvokeCurrentFileChanged(string fileName)
    {
        CurrentFileChangedEventHandler handler = CurrentFileChanged;

        if (handler == null) return;

        handler(fileName);
    }

    private void InvokeCurrentProgressChanged(int percentComplete)
    {
        CurrentProgressChangedEventHandler handler = CurrentProgressChanged;

        if (handler == null) return;

        handler(percentComplete);
    }

    private void InvokeCurrentSpeedUpdated(long bytesPerSecond)
    {
        CurrentWriteSpeedUpdatedEventHandler handler = CurrentWriteSpeedUpdated;

        if (handler == null) return;

        handler(bytesPerSecond);
    }

    private void InvokeTotalProgressChanged(int percentComplete)
    {
        TotalProgressChangedEventHandler handler = TotalProgressChanged;

        if (handler == null) return;

        handler(percentComplete);
    }

    private void InvokeTransportComplete(FileTransportResult result)
    {
        TransportCompleteEventHandler handler = TransportComplete;

        if (handler == null) return;

        handler(result);
    }
}

} }

EDIT: Code added (GUI)编辑:添加代码(GUI)

using System;
using System.IO;
using System.Windows.Forms;
using ExtensionMethods;
using nGenSolutions.IO;

namespace TestApplication
{
public partial class ProgressForm : Form
{
    public ProgressForm()
    {
        InitializeComponent();
    }

    private void ProgressForm_Load(object sender, EventArgs e)
    {
        var transporter = new FileTransporter();
        foreach (string fileName in Directory.GetFiles("C:\\Temp\\"))
        {
            transporter.AddFile(fileName, "C:\\" + Path.GetFileName(fileName));
        }

        transporter.CurrentFileChanged += transporter_CurrentFileChanged;
        transporter.CurrentProgressChanged += transporter_CurrentProgressChanged;
        transporter.TotalProgressChanged += transporter_TotalProgressChanged;
        transporter.CurrentWriteSpeedUpdated += transporter_CurrentWriteSpeedUpdated;
        transporter.TransportComplete += transporter_TransportComplete;

        transporter.BeginTransport();
    }

    void transporter_TransportComplete(FileTransportResult result)
    {
        Close();
    }

    void transporter_CurrentWriteSpeedUpdated(long bytesPerSecond)
    {
        double megaBytesPerSecond = (double)bytesPerSecond/1024000;

        currentSpeedLabel.SafeInvoke(x=> x.Text = string.Format("Transfer speed: {0:0.0} MB/s", megaBytesPerSecond));
    }

    private void transporter_TotalProgressChanged(int percentComplete)
    {
        totalProgressBar.SafeInvoke(x => x.Value = percentComplete);
    }

    private void transporter_CurrentProgressChanged(int percentComplete)
    {
        currentProgressBar.SafeInvoke(x => x.Value = percentComplete);
    }

    private void transporter_CurrentFileChanged(string fileName)
    {
        this.SafeInvoke(x => x.Text = string.Format("Current file: {0}", fileName));
    }
}

} }

EDIT: SafeInvoke code added编辑:添加了 SafeInvoke 代码

public static void SafeInvoke<T>(this T @this, Action<T> action) where T : Control
    {
        if (@this.InvokeRequired)
        {
            @this.Invoke(action, new object[] {@this});
        }
        else
        {
            if (!@this.IsHandleCreated) return;

            if (@this.IsDisposed)
                throw new ObjectDisposedException("@this is disposed.");

            action(@this);
        }
    }

Well, if transporter_CurrentProgressChanged gets correct values, the program works properly.好吧,如果 transporter_CurrentProgressChanged 得到正确的值,程序就可以正常工作。 You can try to add some minimal Thread.Sleep call to InvokeCurrentProgressChanged (maybe with 0 parameter) when progress value is 100%, to get UI chance to update itself, but in this case you reduce the program performance.当进度值为 100% 时,您可以尝试向 InvokeCurrentProgressChanged(可能带有 0 参数)添加一些最小的 Thread.Sleep 调用,以获得更新自身的 UI 机会,但在这种情况下,您会降低程序性能。 It is possibly better to leave the program unchanged, since it works as expected, and main progress bar is updated.最好让程序保持不变,因为它按预期工作,并且主进度条已更新。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM