C# Backgroundworker在自己的类中处理

C# Backgroundworker在自己的类中处理,c#,wpf,multithreading,backgroundworker,C#,Wpf,Multithreading,Backgroundworker,嗯,我有以下问题,希望你能帮助我: 我想创建一个WPF应用程序,其中包含一个后台工作程序,用于更新RichTextBox和其他UI元素。这个后台工作人员应该处理一些数据,例如处理文件夹的内容,进行一些解析等等。因为我想将尽可能多的代码移到主类之外,所以我创建了一个名为MyProcess.cs的类,正如您在下面看到的(事实上,这个类到目前为止没有多大意义,如果这个问题得到解决,它将被更多的处理元素填充)。一般功能应为: 主窗口:将创建一个字符串数组(名为this.folderContent) 主窗

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

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

  • 主窗口:将创建一个字符串数组(名为
    this.folderContent
  • 主窗口:后台工作程序以此数组作为参数启动
  • 主窗口:将调用
    DoWork()
    方法(我知道,这个方法现在在新线程中运行)
  • MyProcess:基于给定的字符串数组生成(到目前为止尚未格式化)段落
  • 主窗口:如果后台工作程序完成,则调用
    RunWorkerCompleted()
    方法(在UI线程中运行),该方法应通过该方法的返回参数更新WPF RichTextBox
  • 最后一步导致InvalidOperationsException,注意“调用线程无法访问此对象,因为另一个线程拥有它。”我读了一些关于后台工作者类及其功能的内容。因此,我认为它与
    MyProcess的
    Execute()
    方法中的
    this.formattedfilenames.Inlines.Add(newrun(…)
    调用有关。如果我将段落属性替换为字符串列表或类似的内容(无需额外的
    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);
        ...
    
    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;
        }
    }
    
    编辑:由于已被询问:RunWorkerAsync被调用,例如在按钮单击事件中或通过对话框选择文件夹后,因此在UI线程中调用

    MyProcess.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);
        ...
    
    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;
        }
    }
    
    类MyProcess
    {
    段落格式文件名;
    公共进程()
    {
    this.formattedfilenames=新段落();
    }
    public void Execute(字符串[]folderContent)
    {
    this.formattedfilenames=新段落();
    如果(folderContent.Length>0)
    {
    对于(int f=0;f
    您是否尝试使用调度程序调用最后一个代码块

    例如:

    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);
    }
    

    有关dispatcher的更多信息,请参见:

    显然,
    段落
    对象(及其子对象)需要线程关联。也就是说,它不是线程安全的,并且设计为仅在创建它的同一线程上使用

    您可能正在从主UI线程调用
    RunWorkerAsync
    ,这就是最终调用
    worker\u RunWorkerCompleted
    的地方。因此,您可以在工作完成后访问主线程上的
    段落
    实例。但是,它是在
    进程内的后台工作线程上创建的。这就是为什么当您从主线程触摸它时会出现
    invalidoOperationsException
    异常

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

    相反,您应该在UI线程上逐个运行后台操作。您可以使用,也可以方便地使用(针对.NET 4.5或.NET 4.0和VS2012+):

    还可以使用事件模式,使代码流与处理
    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());
    
    //fire ExecuteCompleted并传递TaskCompletedEventArgs
    类TaskCompletedEventArgs:EventArgs
    {
    公共任务CompletedEventArgs(任务任务)
    {
    这个。任务=任务;
    }
    公共任务任务{get;private set;}
    }
    EventHandler ExecuteCompleted=(s,e)=>{};
    CancellationTokenSource _cts=null;
    任务_executeTask=null;
    // ... 
    _cts=新的CancellationTokenSource();
    _executeTask=DouithReadWorkLegacySync(_cts.Token);
    //不要在这里等待
    var continuation=\u executeTask.continuateWith(
    task=>this.ExecuteCompleted(此,新TaskCompletedEventArgs(task)),
    _中旅代币,
    TaskContinuationOptions.Executes同步执行,
    TaskScheduler.FromCurrentSynchronizationContext());
    

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

    +1对于这样一个布局良好的帖子,对于一个有起始代表和第一个问题的人,您在哪里调用
    this.worker.RunWorkerAsync(this.folderContent)出于好奇,您的目标是.NET framework的哪个版本?如果您可以访问4.5I的新异步/等待功能,这些操作将变得容易得多。当我使用.NET 4.5时,会调用RunWorkerAsync,例如,在通过对话框选择目录后或在向上移动到父文件夹后(也通过按钮事件)。。。谢谢你的评论,我会尽力的