5

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

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: 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

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

Edit: 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;
    }
}
Daniel
  • 61
  • 5
  • +1 for such a well laid out post for someone with starting rep and a first question – Jay Feb 13 '14 at 19:32
  • Where are you calling `this.worker.RunWorkerAsync(this.folderContent);` – Servy Feb 13 '14 at 19:35
  • 2
    Out of curiosity what version of the .NET framework are you targeting? These sorts of operations become a lot easier if you have access to the new async/await features of 4.5 – Jesse Carter Feb 13 '14 at 19:36
  • I'm using .NET 4.5, the RunWorkerAsync is called, e.g. after selecting a directory via a dialog or after moving up to the parent folder (also via a button event)... Thank you so far for all your comments, I will try it tomorrow and report. – Daniel Feb 13 '14 at 21:21

2 Answers2

2

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.

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.

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.

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

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.

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

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

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.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    Thank you, finally, I used your solution with async/await and it works as expected. I thought, that a backgroundworker is a elegant way to process data, but as you mentioned this does not work (well) for Paragraphs. By-the-way it is also possible to create a progress status flag inside of the class (e.g. `double progress = 0.0;`), increase the value inside of the `for` loop of the `Execute()` method and retrieve this value via another `async` method inside of the main program to update e.g. a progressbar. Again: Thank you all for your help! – Daniel Feb 14 '14 at 21:51
  • @Daniel, no problem. Ideally, you should be using the MVVM pattern and update ViewModel inside a task like `Execute`. It might also be a good idea to reduce the number of operations like `Execute ` running in parallel on the UI thread (note, they are not running concurrently, rather they are getting a slice of execution time on the UI thread, step by step). – noseratio Feb 14 '14 at 21:58
1

Have you tried to use a dispatcher to invoke the last code block?

Example:

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

More info on the dispatcher here: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher(v=vs.110).aspx

Jay
  • 9,561
  • 7
  • 51
  • 72
  • The whole *point* of a BGW is that it marshals the completed event (as well as all other events besides `DoWork` to the UI thread. – Servy Feb 13 '14 at 19:34
  • @Servy yes - but only if invoked from the UI thread. According to the comments on MSDN (http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx), the RunWorkerCompleted event are fired on the UI thread or the thread the RunWorkerAsync method was called from. Since we don't know which thread RunWorkerAsync() is called from - using the dispatcher won't hurt, and will ensure that the code is being updated on the UI thread. – Jay Feb 13 '14 at 19:51
  • 2
    The solution there is *to call `RunWorkerAsync` from the UI thread, not to invoke in the completed handler. If you're gonig to do that there's no point in using BGW in the first place; you may as well just use a `Thread` directly. – Servy Feb 13 '14 at 19:52
  • Yes, you are right - using a thread or thread pool is always going to more efficient when you don't care about marshalling back to a specific thread via a synchronizationContext - but that's a far bigger code change than just sticking a call to the Dispatcher in the handler - and looking at what it's being used for - the OP will never notice the difference in his application. You should post that as an answer. – Jay Feb 13 '14 at 20:00
  • The question, as it stands, does not provide enough information for a proper answer, hence my clarifying question. If it's answered, I may be able to post an answer. As it is, you gave an improper answer to try to compensate for the lack of information in the question. That's unfortunate, and is harmful, not helpful. – Servy Feb 13 '14 at 20:05
  • 1
    Given the edit to the question, it's clear that the `worker_RunWorkerCompleted` method is *already* being called from the UI thread, and this this will not fix anything at all. – Servy Feb 13 '14 at 21:30