-3

I want add text to RichTextBox that not freeze or crash my program. When I use:

await Task.Run(()=> rtb.AppendText("This is a test"));

I get error:

The calling thread cannot access this object because a different thread owns it

So how can I fix it?
And I saw this:

Adding text to RichTextBox Async #C / WPF

More info: I forgot to add that I am adding more than 30k information from txt to database, so when every information exist I want to add it to RichTextBox that exist in database.

Before removing or closing my question, I searched a lot in Google and I can't find any solution for my problem.

Theodor Zoulias
  • 24,585
  • 5
  • 40
  • 69
Saman
  • 5
  • 4
  • i forgot to add that i am adding mor than 30k information from txt to database so when every information exist i want to add it to RichTextBox that exist in database. there is no problem when adding information but only when exist and i want to print message that cause my program freeze – Saman Feb 18 '22 at 12:49
  • In that case create the text you want just once and append it with a single call. You can't modify the UI from a background thread in any operating system. – Panagiotis Kanavos Feb 18 '22 at 12:52
  • 2
    So this is an [XY-Problem](https://xyproblem.info/) - You should always ask about your **real** problem – Sir Rufo Feb 18 '22 at 12:53
  • Are you trying to use the RTF Box as some kind of log viewer? In that case, there are far better options, eg use a ListView and bind it to a `List` or `ObservableCollection`, with an item template that displays the data they way you want. Modifying the backend data will also update the UI – Panagiotis Kanavos Feb 18 '22 at 12:56
  • 1
    @PanagiotisKanavos IMHO you have the same problem, when adding data to an ObservableCollection from other threds – Sir Rufo Feb 18 '22 at 12:59
  • yes that's write. it's for log viewer. i will test it. @sir-rufo that's my real problem. – Saman Feb 18 '22 at 13:00
  • Saman you might find the `Progress` class useful. You can find an example in [this](https://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker/64620920#64620920) answer. – Theodor Zoulias Feb 18 '22 at 13:05
  • @TheodorZoulias I guess - because OP will not talk about his real problem - that the code append lines in such a high frequency, so that the UI will freeze because of this "DoS" attack ;o). In that case even `IProgress` will not help. But as I said, it is a wild guess – Sir Rufo Feb 18 '22 at 13:07
  • @SirRufo it's quite possible that your wild guess is also a correct guess. :-) – Theodor Zoulias Feb 18 '22 at 13:13
  • @SirRufo there's no need for multiple threads to begin with. The real problem would be adding 30K items causing 30K refreshes, or adding 1 item every second, causing refreshes. Personally, I'd go with List<> and manually raise `INotificationChanged` in a throttled manner. There are ways to handle all these problems though, even if multiple threads wanted to modify the backing list. What *can't* be solved, is trying to modify the text in an RTF. – Panagiotis Kanavos Feb 18 '22 at 13:20
  • @SirRufo with 30K items, [virtualization](https://docs.microsoft.com/en-us/dotnet/desktop/wpf/advanced/optimizing-performance-controls?view=netframeworkdesktop-4.8) is needed to ensure only the visible items will be rendered too. – Panagiotis Kanavos Feb 18 '22 at 13:22
  • @Saman how often do you load or append new data? Is this a one-of operation, do you refresh all lines every 10 seconds, do you only load new lines every 1 second? It matters. Also why did you use an RTF Box? Do you want to format the messages? All of this matters. There are better ways to do this in WPF – Panagiotis Kanavos Feb 18 '22 at 13:28
  • @Saman even if you want to load everything and display it in an RTF box, you can simply load the data in an async method, create a FlowDocument from it then just set that to the RTB's `Document` property. – Panagiotis Kanavos Feb 18 '22 at 13:36
  • You may however not be able to create the FlowDocument in a thread other than the UI thread. A FlowDocument is a DependencyObject, but no Freezable. Cross-thread access might not be possible. – Clemens Feb 18 '22 at 13:40
  • @Clemens that wouldn't be a problem with a *new* FlowDocument. On the other hand, one can create the XAML markup for the FlowDocument then generate a block with XamlParser. That's the worst-case scenario though. I'd rather bind a ListView with a "message" template to a List and set up ListView for virtualization. Even with high-volume real-time data, only loading the data would have to be async. Appending a new object to a list isn't expensive and doesn't have to be done in the background. `INotifyPropertyChanged` should be called periodically to avoid refreshing for every little change – Panagiotis Kanavos Feb 18 '22 at 13:51
  • @PanagiotisKanavos It would. Try `rtb.Document = await Task.Run(() => new FlowDocument());` – Clemens Feb 18 '22 at 13:54

1 Answers1

-1

Disclaimer: the code below may contain small syntax error as I didn't proof it via a compiler, however they show the general idea.

So, there are two options, which have mostly the same machinery under the hood:

  1. SynchronizationContext

    // Call this from the main thread to capture the context
    var ctx = SynchronizationContext.Current
    ....
    // Somewhere later reuse that context with scheduling your lambda code
    // The context to call it once main thread is idle
    ctx.Post((_)=> rtb.AppendText("This is a test"), null);
    
  2. Dispatcher

    // schedule lambda invocation
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(()=> rtb.AppendText("This is a test"));
    

So, unlike Task.Run which is scheduled for a worker thread from a thread pool, we explicitly tying us up to the main thread and its message queue. The lambda is placed to the queue, and would be called only if the main thread is idle (so it goes to process the messages from the queue).

P.S. Maybe you'll need

using System.Threading;
using System.Windows.Threading;
Yury Schkatula
  • 4,962
  • 2
  • 17
  • 41
  • Any control is derived from `DispatcherObject` so you can ask the control itself for the right Dispatcher. `rtb.Dispatcher.BeginInvoke( ... )` – Sir Rufo Feb 18 '22 at 12:51
  • Thanks but non of them worked. 1. it's say "DelegateSendOrPostCallBack dose not take 0 argument 2. because use that code in another class. there is no Current Method – Saman Feb 18 '22 at 12:56
  • @Saman the problem isn't how to append asynchronously. The very idea of using an RTF textbox as a live log viewer can't scale – Panagiotis Kanavos Feb 18 '22 at 13:25
  • @Saman I updated the code a bit, to be compilable for sure – Yury Schkatula Feb 18 '22 at 14:11