Csharp/C#教程:后台工作者在自己的class级处理分享


后台工作者在自己的class级处理

好吧,我有以下问题,希望你能帮助我:

我想创建一个带后台工作程序的WPF应用程序,用于更新richtextboxes和其他UI元素。 这个后台工作者应该处理一些数据,例如处理文件夹的内容,做一些解析等等。 因为我想在Main类之外移动尽可能多的代码,所以我创建了一个名为MyProcess.cs类,如下所示(事实上,到目前为止,这个类没有多大意义,它将填充更多处理元素,如果这个问题已经解决)。 一般function应该是:

  1. MainWindow:将创建一个字符串数组(名为this.folderContent
  2. MainWindow:后台工作者开始将此数组作为参数
  3. MainWindow:将调用DoWork()方法(我知道,这个方法现在在一个新线程中运行)
  4. MyProcess:根据给定的字符串数组生成一个(迄今为止未格式化的)段落
  5. 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 。 这就是当你从主线程触摸它时,你得到InvalidOperationsExceptionexception的原因。

如果上述对问题的理解是正确的,您可能应该放弃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列出了一些很棒的资源。

要像上面那样调用Executeasync实现,您需要采用“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.IsCancelledTask.IsFaultedTask.ExceptionTask.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

(0)
上一篇 2022年1月3日
下一篇 2022年1月3日

精彩推荐