简体   繁体   中英

How can I fit my code into a Background Worker?

I've built out a form that does the following:

  1. Grabs all the file paths in a directory (files are in a specified naming convention)
  2. Parses file paths using a delimiter
  3. Outputs a text file with a specified format.

The issue is that some directories are hundreds of gigs which freezes the form UI until the process is done. I've done some reading on Background workers and to my understanding, the data intensive part of your code goes under the DoWork method. However when I try implementing my code into the DoWork method I run into the "Cross-thread operation not valid:" error. I looked up the error but I simply don't understand how I can work around this error without reworking the entire structure of my program.

My code is quite lengthy but I can supply if needed.

The best way is to rework your code so it doesn't use UI components.

Once it's done you can use ReportProgress/ProgressChanged and RunWorkerCompleted to refresh the UI.

If you still want to use UI components, you may try Invoke :

this.Invoke(new Action(() => 
    {
        //code that uses UI for instance :
        //this.TextBox1.Text = "test"
    });

The most likely culprit is that you are accessing values held by controls from your background worker. One can neither write , nor read , from those objects unless one invoke's back to the GUI thread. Long story short I ran into the same issue when trying to read a value from a textbox from my do work thread which, even though it was a read, it failed.

I recommend you remove all data access/update from the background worker to resolve this problem.

Here is the articles I wrote about the background worker

C# WPF: Linq Fails in BackgroundWorker DoWork Event

C# WPF: Threading, Control Updating, Status Bar and Cancel Operations Example All In One

The error is occuring when you are trying to write to a control on the form from the BackGroundWorker. You need to add InVoke when you write the control. See following webpage : https://msdn.microsoft.com/en-us/library/ms171728%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396

Your code accesses properties of GUI controls from the thread other than the one they are created on. WindowsForms/WPF internally checks it, sees that you are accessing control's property from another thread and throws an exception.

I would advise you to use the MVVM pattern. Also, BackgroundWorker is considered obsolete now and you should use Tasks with async/await keywords instead. Here is how I would do it in a WPF application:

public class ViewModel : INotifyPropertyChanged
{
    ...

    public string PathInfo { ... } // raises INotifyPropertyChanged.PropertyChanged event from the setter
    public RelayCommand ProcessPathsCommand { get; set; }

    public ViewModel()
    {
        ProcessPathsCommand = new RelayCommand(ProcessPaths);
    }

    public async void ProcessPaths()
    {
        // disable the command, which will lead to disabling a button bound to the command
        ProcessPathsCommand.IsEnabled = false;

        try
        {
            // run processing on another thread
            await Task.Run(() =>
            {
                // emulate hard-work
                Thread.Sleep(5000);
                // update the property on the view model, which will lead to updating a textblock bound to this property                
                // in WPF you can update the bound property right from another thread and WPF will automatically dispatch PropertyChanged event to the main UI thread. In WinForms this would lead to an exception.
                PathInfo = "Here are the results: bla bla bla";
            });
            // In WinForms you would need to update the bound property here:
            // Thanks to Tasks and await this line of code will be executed only after the task is finished
            // PathInfo = resultFromTaskThread;
        }
        finally
        {
            ProcessPathsCommand.IsEnabled = true;
        }
    }

Many of us remember that in WindowsForms we had to use Control.Invoke() to update GUI controls from another thread (and many of us still use Dispatcher.Invoke in WPF even when it's not necessary). With WPF Microsoft finally made it easier for us! WPF automatically dispatches PropertyChanged events to the main GUI thread, so can we simply update a property (bound to some control on UI) on a view model from another thread, the property setter will raise PropertyChanged event, and WPF will automatically dispatch this event to the UI thread!
You can also read about this here:
Multithreading and Dispatching in MVVM Applications

Feel free to let me know if you need more details.

If TB_path or TB_TrackingNum are UI elements (and I suspect they are) then you are trying to access the UI.

If you just bind the UI elements to public properties you can access the public properties from the BackgroundWorker.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM