从后台线程问题更新UI

时间:2011-06-12 13:54:08

标签: c# multithreading user-interface

我正在编写一个类来移动和复制文件。我在当前文件进度和总进度发生变化时引发事件。当我在我的XP机器上测试代码时,它工作正常,但是当我在Windows 7 64位机器上运行它时,当前进度不能正确更新UI。当前进度ProgressBar仅获得一半,然后在下一个执行相同操作的文件上启动。总进度ProgressBar更新正常。任何想法为什么会发生这种情况?

编辑:Windows 7机器运行四核,XP正在运行双核。不确定这可能会产生什么影响。我只是一个业余爱好者,所以请原谅我的无知:)

编辑:添加了代码(背景)

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);
    }
}

}

编辑:添加代码(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));
    }
}

}

编辑:添加了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);
        }
    }

1 个答案:

答案 0 :(得分:1)

好吧,如果transporter_CurrentProgressChanged获得正确的值,程序就能正常工作。当进度值为100%时,您可以尝试向InvokeCurrentProgressChanged(可能带有0参数)添加一些最小的Thread.Sleep调用,以获得UI更新自身的机会,但在这种情况下,您会降低程序性能。最好不要让程序保持不变,因为它按预期工作,主进度条会更新。