Обновление интерфейса от проблем с фоновыми потоками - PullRequest
0 голосов
/ 12 июня 2011

Я кодирую класс для перемещения и копирования файлов. Я поднимаю события, когда текущий прогресс файла и общий прогресс изменяется. Когда я тестирую код на моей машине с XP, он работает нормально, но когда я запускаю его на моей 64-битной машине с Windows 7, текущий прогресс не обновляет интерфейс правильно. Текущий прогресс 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 Ответ

1 голос
/ 12 июня 2011

Хорошо, если transporter_CurrentProgressChanged получает правильные значения, программа работает правильно. Вы можете попытаться добавить минимальный вызов Thread.Sleep к InvokeCurrentProgressChanged (возможно, с параметром 0), когда значение хода выполнения равно 100%, чтобы получить возможность обновления пользовательского интерфейса, но в этом случае вы снижаете производительность программы. Возможно, лучше оставить программу без изменений, поскольку она работает, как ожидается, и основной индикатор выполнения обновляется.

...