如何从WPF中的BackgroundWorker线程直接访问UI线程?

时间:2009-11-21 15:52:36

标签: c# wpf xaml multithreading backgroundworker

我正在WPF中创建一个备份实用程序,并且有关于线程的一般性问题

在方法 backgroundWorker.DoWork()中,语句Message2.Text =“...”给出错误“调用线程无法访问此对象,因为另一个线程拥有它”。

我无法直接访问backgroundWorker.DoWork()中的UI线程,即在那时更改XAML TextBox中的文本?或者我是否需要将所有显示信息存储在内部变量中,然后将其显示在 backgroundWorker.ProgressChanged()中,因为我必须使用例如percentageFinished?

XAML:

<Window x:Class="TestCopyFiles111.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1"  Height="350" Width="525">
    <DockPanel LastChildFill="True" HorizontalAlignment="Left" VerticalAlignment="Top"
                Margin="10">

        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
            <Button x:Name="Button_Start" 
                    HorizontalAlignment="Left"  
                    DockPanel.Dock="Top" 
                    Content="Start Copying" 
                    Click="Button_Start_Click" 
                    Height="25" 
                    Margin="0 0 5 0"
                    Width="200"/>
            <Button x:Name="Button_Cancel" 
                    HorizontalAlignment="Left"  
                    DockPanel.Dock="Top" 
                    Content="Cancel" 
                    Click="Button_Cancel_Click" 
                    Height="25" 
                    Width="200"/>
        </StackPanel>

        <ProgressBar x:Name="ProgressBar"
                     DockPanel.Dock="Top" 
                     HorizontalAlignment="Left"
                    Margin="0 10 0 0"
                    Height="23"
                     Width="405"
                     Minimum="0"
                     Maximum="100"
                     />

        <TextBlock DockPanel.Dock="Top" x:Name="Message" Margin="0 10 0 0"/>
        <TextBlock DockPanel.Dock="Top" x:Name="CurrentFileCopying" Margin="0 10 0 0"/>
        <TextBlock DockPanel.Dock="Top" x:Name="Message2" Margin="0 10 0 0"/>
    </DockPanel>
</Window>

代码隐藏:

using System.Windows;
using System.ComponentModel;
using System.Threading;
using System.IO;
using System.Collections.Generic;
using System;

namespace TestCopyFiles111
{
    public partial class Window1 : Window
    {
        private BackgroundWorker backgroundWorker;

        float percentageFinished = 0;
        private int totalFilesToCopy = 0;
        int filesCopied = 0;

        string currentPathAndFileName;

        private List<CopyFileTask> copyFileTasks = new List<CopyFileTask>();
        private List<string> foldersToCreate = new List<string>();

        public Window1()
        {
            InitializeComponent();
            Button_Cancel.IsEnabled = false;
            Button_Start.IsEnabled = true;
            ProgressBar.Visibility = Visibility.Collapsed;

        }

        private void Button_Start_Click(object sender, RoutedEventArgs e)
        {
            Button_Cancel.IsEnabled = true;
            backgroundWorker = new BackgroundWorker();
            backgroundWorker.WorkerReportsProgress = true;
            backgroundWorker.WorkerSupportsCancellation = true;
            ProgressBar.Visibility = Visibility.Visible;

            AddFilesFromFolder(@"c:\test", @"C:\test2");

            Message.Text = "Preparing to copy...";

            MakeSureAllDirectoriesExist();

            CopyAllFiles();

        }


        void AddFilesFromFolder(string sourceFolder, string destFolder)
        {
            if (!Directory.Exists(destFolder))
                Directory.CreateDirectory(destFolder);
            string[] files = Directory.GetFiles(sourceFolder);
            foreach (string file in files)
            {
                string name = Path.GetFileName(file);
                string dest = Path.Combine(destFolder, name);
                copyFileTasks.Add(new CopyFileTask(file, dest));
                totalFilesToCopy++;
            }
            string[] folders = Directory.GetDirectories(sourceFolder);
            foreach (string folder in folders)
            {
                string name = Path.GetFileName(folder);
                string dest = Path.Combine(destFolder, name);
                foldersToCreate.Add(dest);
                AddFilesFromFolder(folder, dest);
            }
        }

        void MakeSureAllDirectoriesExist()
        {
            foreach (var folderToCreate in foldersToCreate)
            {
                if (!Directory.Exists(folderToCreate))
                    Directory.CreateDirectory(folderToCreate);
            }
        }

        void CopyAllFiles()
        {
            backgroundWorker = new BackgroundWorker();
            backgroundWorker.WorkerReportsProgress = true;
            backgroundWorker.WorkerSupportsCancellation = true;

            backgroundWorker.DoWork += (s, args) =>
            {
                filesCopied = 0;
                foreach (var copyFileTask in copyFileTasks)
                {
                    if (backgroundWorker.CancellationPending)
                    {
                        args.Cancel = true;
                        return;
                    }

                    DateTime sourceFileLastWriteTime = File.GetLastWriteTime(copyFileTask.SourceFile);
                    DateTime targetFileLastWriteTime = File.GetLastWriteTime(copyFileTask.TargetFile);

                    if (sourceFileLastWriteTime != targetFileLastWriteTime)
                    {
                        Message2.Text = "dates are not the same";
                    }
                    else
                    {
                        Message2.Text = "dates are the same";
                    }

                    if (!File.Exists(copyFileTask.TargetFile))
                        File.Copy(copyFileTask.SourceFile, copyFileTask.TargetFile);

                    currentPathAndFileName = copyFileTask.SourceFile;

                    UpdatePercentageFinished();
                    backgroundWorker.ReportProgress((int)percentageFinished);

                    filesCopied++;
                }

            };

            backgroundWorker.ProgressChanged += (s, args) =>
            {
                percentageFinished = args.ProgressPercentage;
                ProgressBar.Value = percentageFinished;
                Message.Text = percentageFinished + "% finished";
                CurrentFileCopying.Text = currentPathAndFileName;
            };

            backgroundWorker.RunWorkerCompleted += (s, args) =>
            {
                Button_Start.IsEnabled = true;
                Button_Cancel.IsEnabled = false;
                ProgressBar.Value = 0;
                UpdatePercentageFinished();
                CurrentFileCopying.Text = "";

                if (percentageFinished < 100)
                {
                    Message.Text = String.Format("cancelled at {0:0}% finished", percentageFinished);
                }
                else
                {
                    Message.Text = "All files copied.";
                }
            };

            backgroundWorker.RunWorkerAsync();
        }

        void UpdatePercentageFinished()
        {
            percentageFinished = (filesCopied / (float)totalFilesToCopy) * 100f;
        }


        class CopyFileTask
        {
            public string SourceFile { get; set; }
            public string TargetFile { get; set; }
            public CopyFileTask(string sourceFile, string targetFile)
            {
                SourceFile = sourceFile;
                TargetFile = targetFile;
            }
        }

        private void Button_Cancel_Click(object sender, RoutedEventArgs e)
        {
            backgroundWorker.CancelAsync();
        }

    }
}

3 个答案:

答案 0 :(得分:7)

你看过使用Dispatcher.Invoke吗?

Dispatcher.Invoke(new Action(() => { Button_Start.Content = i.ToString(); }));

如果您想要异步发生某些事情,请使用BeginInvoke。

答案 1 :(得分:2)

您最好的选择是继续使用.ReportProgress和.ProgressChanged。有什么特别的原因这还不够吗?

答案 2 :(得分:2)

您无法直接从其他线程访问UI。唯一的解决方案是在线程中引发一个事件,然后在UI线程中捕获它。

如果你不想使用BackgroundWorker线程,你需要这样的东西来提升线程中的事件:

        // Final update
        if (Library_Finished != null)
        {
            Library_Finished(this, null);
        }

声明如下:

    public event EventHandler Library_Finished;

然后在UI线程中需要这样的东西来捕获和处理事件:

    private void Library_Finished(object sender, EventArgs e)
    {
        Action action = () => FinalUpdate();
        if (Thread.CurrentThread != Dispatcher.Thread)
        {
            Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, action);
        }
        else
        {
            action();
        }
    }

但即使你使用BackgroundWorker,你仍然需要在访问UI元素之前实现线程检查代码。