自己班级的背景工作者处理 [英] Backgroundworker processing in own class

查看:56
本文介绍了自己班级的背景工作者处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好,我遇到以下问题,希望您能为我提供帮助:

Well, I have the following problem and I hope that you can help me:

我想创建一个带有后台工作程序的WPF应用程序,以更新Richtextboxes和其他UI元素.该后台工作人员应处理一些数据,例如处理文件夹的内容,进行一些解析等等.由于我想将尽可能多的代码移到Main类之外,因此我在下面创建了一个名为MyProcess.cs的类(实际上,到目前为止该类没有多大意义,它将填充更多内容)处理元素(如果此问题已解决).常规功能应为:

I would like to create a WPF application with a background worker for updating richtextboxes and other UI elements. This background worker should process some data, e.g. handle the content of a folder, do some parsing and much more. Since I would like to move as much code as possible outside the Main class, I created a class called MyProcess.cs as you can see below (in fact, this class does not make much sense so far, it will be filled with much more processing elements if this problem has been solved). The general functionality should be:

  1. MainWindow:将创建一个字符串数组(名为this.folderContent)
  2. MainWindow:后台工作人员开始使用此数组作为参数
  3. MainWindow:DoWork()方法将被调用(我知道,该方法现在在新线程中运行)
  4. MyProcess:根据给定的字符串数组生成一个(迄今为止未格式化的)段落
  5. MainWindow:如果后台工作人员完成,则会调用RunWorkerCompleted()方法(在UI线程中运行),该方法应通过该方法的返回参数更新WPF RichTextBox
  1. MainWindow: An array of strings will be created (named this.folderContent)
  2. MainWindow: A background worker is started taking this array as argument
  3. MainWindow: The DoWork() method will be called (I know, this one now runs in a new thread)
  4. MyProcess: Generates a (so far unformatted) Paragraph based on the given string array
  5. MainWindow: If the background worker is finished, the RunWorkerCompleted() method is called (running in UI thread) which should update a WPF RichTextBox via the method's return argument

最后一步会导致InvalidOperationsException,并带有以下注释:调用线程无法访问此对象,因为其他线程拥有它."我读了一些有关后台工作者类及其功能的文章.因此,我认为这与MyProcessExecute()方法中的this.formatedFilenames.Inlines.Add(new Run(...))调用有关.如果我用字符串列表或类似内容(没有附加的new()调用)替换Paragraph属性,则可以通过get方法访问此成员而不会出现任何问题.我发现与后台工作人员相关的所有示例仅返回基本类型或简单类.

This last step causes an InvalidOperationsException with the note, that "The calling thread cannot access this object because a different thread owns it." I read a bit about the background worker class and its functionality. So I think that it has something to do with the this.formatedFilenames.Inlines.Add(new Run(...)) call in the Execute() method of MyProcess. If I replace the Paragraph attribute by a list of strings or something similar (without additional new() calls) I can access this member without any problems by a get method. All examples related to the background worker I have found return only basic types or simple classes.

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线程中.

Since this has been asked: the RunWorkerAsync is called e.g. on a button click event or after a folder has been selected via a dialog, so within the UI thread.

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

Apparently, the Paragraph object (and its sub-objects) requires thread affinity. That is, it is not thread-safe and was designed to be used only on the same thread it was created.

大概是从主UI线程调用RunWorkerAsync的地方,而这正是最终调用worker_RunWorkerCompleted的地方.这样,您在工作完成后访问主线程上的Paragraph实例.但是,它是在process.Execute内部的后台工作线程上创建的.这就是为什么从主线程中触摸到它时会得到InvalidOperationsException异常的原因.

Presumably, you're calling the RunWorkerAsync from the main UI thread, and that's where worker_RunWorkerCompleted is eventually called. Thus, you access the instance of Paragraph on the main thread upon completion of the work. However, it was created on the background worker thread, inside process.Execute. That's why you're getting the InvalidOperationsException exception when you touch it from the main thread.

如果对问题的上述理解是正确的,则您可能应该放弃BackgroundWorker.使用后台线程运行for循环没有多大意义,唯一的目的是通过Dispatcher.Invoke将回调编组为UI线程.那只会增加额外的开销.

If the above understanding of the problem is correct, you should probably give up on the BackgroundWorker. It doesn't make a lot of sense to use a background thread to run a for loop, the only purpose of which would be to marshal callbacks to the UI thread via Dispatcher.Invoke. That'd only add an extra overhead.

相反,您应该在UI线程上逐段运行后台操作.您可以使用 DispatcherTimer ,或者您可以方便地使用 async/await (针对.NET 4.5带有 Microsoft.Bcl.Async .NET 4.0 VS2012 +):

Instead, you should run your background operation on the UI thread, piece by piece. You could use DispatcherTimer for that, or you could conveniently run it with async/await (targeting .NET 4.5 or .NET 4.0 with Microsoft.Bcl.Async and 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);
        }
    }
}

async/await上有一些学习曲线,但是当然值得一试. async-await标签Wiki 列出了一些很好的资源.

There's some learning curve when it comes to async/await, but it's certainly well worth taking it. The async-await tag wiki lists some great resources, to start with.

要像上面一样调用Executeasync实现,则需要包含一路异步" 规则.通常,这意味着您将从顶级事件或命令处理程序(也为async)中调用Execute,并在其结果中调用await,例如:

To call an async implementation of Execute like above, you'd need to embrace the "Async all the way" rule. Usually, it means you'd call Execute from a top-level event or command handler which is also async, and await its result, e.g.:

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的原始方案更相似:

It's also possible to use an event pattern, to make the code flow more similar to your original scenario where you handle 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());

在这种情况下,您应该在ExecuteCompleted事件处理程序中显式检查Task对象属性,例如Task.IsCancelledTask.IsFaultedTask.ExceptionTask.Result.

In this case, you should explicitly check the Task object properties like Task.IsCancelled, Task.IsFaulted, Task.Exception, Task.Result inside your ExecuteCompleted event handler.

这篇关于自己班级的背景工作者处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆