Dan*_*iel 5 c# wpf multithreading backgroundworker
好吧,我有以下问题,希望你能帮助我:
我想创建一个带后台工作程序的WPF应用程序,用于更新richtextboxes和其他UI元素.这个后台工作者应该处理一些数据,例如处理文件夹的内容,做一些解析等等.因为我想在Main类之外移动尽可能多的代码,所以我创建了一个类MyProcess.cs,如下所示(事实上,到目前为止,这个类没有多大意义,如果有更多的处理元素,这个问题已经解决了).一般功能应该是:
this.folderContent)DoWork()将调用该方法(我知道,这个现在在一个新线程中运行)RunWorkerCompleted()则调用该方法(在UI线程中运行),该方法应通过方法的返回参数更新WPF RichTextBox最后一步导致带有注释的InvalidOperationsException,"调用线程无法访问此对象,因为另一个线程拥有它".我读了一下后台工作者类及其功能.所以我认为它与方法中的this.formatedFilenames.Inlines.Add(new Run(...))调用Execute()有关MyProcess.如果我用字符串列表或类似的东西替换Paragraph属性(没有额外的new()调用),我可以通过get方法访问此成员而不会出现任何问题.与我发现的后台工作者相关的所有示例都只返回基本类型或简单类.
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
this.process = new MyProcess();
this.worker = new BackgroundWorker();
this.worker.DoWork += worker_DoWork;
this.worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.process.Execute((string[])e.Argument);
e.Result = this.process.Paragraph();
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.rtbFolderContent.Document.Blocks.Clear();
// the next line causes InvalidOperationsException:
// The calling thread cannot access this object because a different thread owns it.
this.rtbFolderContent.Document.Blocks.Add((Paragraph)e.Result);
}
...
// folderContent of type string[]
this.worker.RunWorkerAsync(this.folderContent);
...
Run Code Online (Sandbox Code Playgroud)
编辑:由于已经询问过:例如,在按钮单击事件上或通过对话框选择文件夹后调用RunWorkerAsync,因此在UI线程中.
MyProcess.cs
class MyProcess
{
Paragraph formatedFilenames;
public MyProcess ()
{
this.formatedFilenames = new Paragraph();
}
public void Execute(string[] folderContent)
{
this.formatedFilenames = new Paragraph();
if (folderContent.Length > 0)
{
for (int f = 0; f < folderContent.Length; ++f)
{
this.formatedFilenames.Inlines.Add(new Run(folderContent[f] + Environment.NewLine));
// some dummy waiting time
Thread.Sleep(500);
}
}
}
public Paragraph Paragraph()
{
return this.formatedFilenames;
}
}
Run Code Online (Sandbox Code Playgroud)
显然,该Paragraph对象(及其子对象)需要线程关联性。也就是说,它不是线程安全的,并且被设计为仅在创建它的同一线程上使用。
RunWorkerAsync据推测,您是从主 UI 线程调用的,这就是worker_RunWorkerCompleted最终调用的地方。Paragraph因此,您可以在完成工作后访问主线程上的实例。但是,它是在后台工作线程上创建的,位于process.Execute. InvalidOperationsException这就是为什么当您从主线程触摸它时会收到异常。
如果以上对问题的理解是正确的,你可能应该放弃BackgroundWorker. 使用后台线程运行循环没有多大意义for,其唯一目的是通过Dispatcher.Invoke. 这只会增加额外的开销。
相反,您应该在 UI 线程上逐段运行后台操作。您可以使用DispatcherTimer它,或者您可以方便地使用async/await运行它(针对 .NET 4.5或.NET 4.0Microsoft.Bcl.Async和 VS2012+):
public async Task Execute(string[] folderContent, CancellationToken token)
{
this.formatedFilenames = new Paragraph();
if (folderContent.Length > 0)
{
for (int f = 0; f < folderContent.Length; ++f)
{
token.ThrowIfCancellationRequested();
// yield to the Dispatcher message loop
// to keep the UI responsive
await Dispatcher.Yield(DispatcherPriority.Background);
this.formatedFilenames.Inlines.Add(
new Run(folderContent[f] + Environment.NewLine));
// don't do this: Thread.Sleep(500);
// optionally, throttle it;
// this step may not be necessary as we use Dispatcher.Yield
await Task.Delay(500, token);
}
}
}
Run Code Online (Sandbox Code Playgroud)
虽然有一些学习曲线async/await,但绝对值得一试。async -await 标签 wiki列出了一些很棒的资源。
要调用上面async的实现Execute,您需要接受“始终异步”规则。通常,这意味着您将从Execute顶级事件或命令处理程序调用async,它也是await及其结果,例如:
CancellationTokenSource _cts = null;
async void SomeCommand_Executed(object sender, RoutedEventArgs e)
{
if (_cts != null)
{
// request cancellation if already running
_cts.Cancel();
_cts = null;
}
else
{
// start a new operation and await its result
try
{
_cts = new CancellationTokenSource();
await Execute(this.folderContent, _cts.Token);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Run Code Online (Sandbox Code Playgroud)
还可以使用事件模式,使代码流更类似于您处理的原始场景RunWorkerCompleted:
// fire ExecuteCompleted and pass TaskCompletedEventArgs
class TaskCompletedEventArgs : EventArgs
{
public TaskCompletedEventArgs(Task task)
{
this.Task = task;
}
public Task Task { get; private set; }
}
EventHandler<TaskCompletedEventArgs> ExecuteCompleted = (s, e) => { };
CancellationTokenSource _cts = null;
Task _executeTask = null;
// ...
_cts = new CancellationTokenSource();
_executeTask = DoUIThreadWorkLegacyAsync(_cts.Token);
// don't await here
var continutation = _executeTask.ContinueWith(
task => this.ExecuteCompleted(this, new TaskCompletedEventArgs(task)),
_cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.FromCurrentSynchronizationContext());
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您应该在事件处理程序中显式检查Task对象属性,例如Task.IsCancelled、Task.IsFaulted、Task.Exception、。Task.ResultExecuteCompleted