gpt4 book ai didi

c# - Backgroundworker 在自己的类中处理

转载 作者:太空狗 更新时间:2023-10-29 23:31:24 25 4
gpt4 key购买 nike

那么,我有以下问题,希望你能帮助我:

我想创建一个 WPF 应用程序,其中包含一个用于更新富文本框和其他 UI 元素的后台工作程序。这个后台 worker 应该处理一些数据,例如处理文件夹的内容,进行一些解析等等。由于我想将尽可能多的代码移到 Main 类之外,因此我创建了一个名为 MyProcess.cs 的类正如你在下面看到的(事实上,这个类到目前为止没有多大意义,如果这个问题已经解决,它将填充更多的处理元素)。一般功能应该是:

  1. MainWindow:将创建一个字符串数组(名为 this.folderContent)
  2. MainWindow:后台工作人员开始将此数组作为参数
  3. 主窗口:DoWork()方法将被调用(我知道,这个现在在一个新线程中运行)
  4. MyProcess:根据给定的字符串数组生成一个(到目前为止未格式化的)段落
  5. MainWindow:如果后台 worker 完成,RunWorkerCompleted()方法被调用(在 UI 线程中运行),它应该通过方法的返回参数更新 WPF RichTextBox

这最后一步导致 InvalidOperationsException 并附注“调用线程无法访问此对象,因为另一个线程拥有它。”我阅读了一些关于后台 worker 类及其功能的内容。所以我认为它与 this.formatedFilenames.Inlines.Add(new Run(...)) 有关调用Execute() MyProcess的方法| .如果我用字符串列表或类似的东西替换 Paragraph 属性(没有额外的 new() 调用),我可以通过 get 方法毫无问题地访问这个成员。我发现的所有与后台 worker 相关的示例都只返回基本类型或简单类。

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对象(及其子对象)需要线程关联。也就是说,它不是线程安全的,只能在创建它的同一线程上使用。

据推测,您正在调用 RunWorkerAsync来自主 UI 线程,这就是 worker_RunWorkerCompleted 的地方最终被调用。因此,您访问了 Paragraph 的实例工作完成后在主线程上。但是,它是在 process.Execute 内的后台工作线程上创建的.这就是为什么你得到 InvalidOperationsException当您从主线程触摸它时出现异常。

如果以上对问题的理解是正确的,你大概应该放弃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 tag wiki列出一些很棒的资源,作为开始。

调用async实现Execute像上面一样,你需要接受 "Async all the way"规则。通常,这意味着您会调用 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);
}
}
}

也可以使用事件模式,使代码流更类似于您处理 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());

在这种情况下,您应该明确检查 Task对象属性,如 Task.IsCancelled , Task.IsFaulted , Task.Exception , Task.Result在你的里面ExecuteCompleted事件处理程序。

关于c# - Backgroundworker 在自己的类中处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21763639/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com