后台工作者在自己的class级处理
好吧,我有以下问题,希望你能帮助我:
我想创建一个带后台工作程序的WPF应用程序,用于更新richtextboxes和其他UI元素。 这个后台工作者应该处理一些数据,例如处理文件夹的内容,做一些解析等等。 因为我想在Main类之外移动尽可能多的代码,所以我创建了一个名为MyProcess.cs
类,如下所示(事实上,到目前为止,这个类没有多大意义,它将填充更多处理元素,如果这个问题已经解决)。 一般function应该是:
- MainWindow:将创建一个字符串数组(名为
this.folderContent
) - MainWindow:后台工作者开始将此数组作为参数
- MainWindow:将调用
DoWork()
方法(我知道,这个方法现在在一个新线程中运行) - MyProcess:根据给定的字符串数组生成一个(迄今为止未格式化的)段落
- MainWindow:如果后台工作程序完成,则调用
RunWorkerCompleted()
方法(在UI线程中运行),该方法应通过方法的返回参数更新WPF RichTextBox
最后一步导致带有注释的InvalidOperationsException,“调用线程无法访问此对象,因为另一个线程拥有它”。 我读了一下后台工作者类及其function。 所以我认为它与this.formatedFilenames.Inlines.Add(new Run(...))
的Execute()
方法中的this.formatedFilenames.Inlines.Add(new Run(...))
调用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); ...
编辑:由于已经询问过:例如,在按钮单击事件上或通过对话框选择文件夹后调用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; } }
显然, Paragraph
对象(及其子对象)需要线程关联。 也就是说,它不是线程安全的,并且只能在创建它的同一个线程上使用。
据推测,您从主UI线程调用RunWorkerAsync
,这就是最终调用worker_RunWorkerCompleted
的位置。 因此,您在完成工作后访问主线程上的Paragraph
实例。 但是,它是在后台工作线程上创建的,在process.Execute
。 这就是当你从主线程触摸它时,你得到InvalidOperationsException
exception的原因。
如果上述对问题的理解是正确的,您可能应该放弃BackgroundWorker
。 使用后台线程来运行for
循环没有多大意义,其唯一目的是通过Dispatcher.Invoke
封送回UI线程的回调。 这只会增加额外的开销。
相反,您应该在UI线程上逐个运行后台操作。 您可以使用DispatcherTimer
,或者您可以使用async / await (使用Microsoft.Bcl.Async
和VS2012 +定位.NET 4.5 或 .NET 4.0)方便地运行它:
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); } } }
在async/await
有一些学习曲线,但它当然值得一试。 async-await标签wiki列出了一些很棒的资源。
要像上面那样调用Execute
的async
实现,您需要采用“Async all a way”规则。 通常,它意味着您从顶级事件或命令处理程序(也是async
调用Execute
,并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); } } }
也可以使用事件模式,使代码流更类似于处理RunWorkerCompleted
原始场景:
// fire ExecuteCompleted and pass TaskCompletedEventArgs class TaskCompletedEventArgs : EventArgs { public TaskCompletedEventArgs(Task task) { this.Task = task; } public Task Task { get; private set; } } EventHandler 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());
在这种情况下,您应该在ExecuteCompleted
事件处理程序中显式检查Task
对象属性,如Task.IsCancelled
, Task.IsFaulted
, Task.Exception
, Task.Result
。
您是否尝试使用调度程序来调用最后一个代码块?
例:
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { Action action = () => { 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); }; Dispatcher.Invoke(DispatcherPriority.Normal, action); }
有关调度程序的更多信息,请访问: http : //msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher(v=vs.110).aspx
上述就是C#学习教程:后台工作者在自己的class级处理分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注---计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1018439.html