我正在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();
}
}
}
答案 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元素之前实现线程检查代码。