8

I create a SolidColorBrush on some non-GUI thread, and want to pass it to a GUI thread to display it, but I get InvalidOperationException: The calling thread cannot access this object because a different thread owns it. (even if I try to Freeze(); it). How do I pass object that was created in thread X to thread Y ?

I know I can create this SolidColorBrush object in the GUI thread with Dispatcher, but that would complicate everything... I want to create it in the worker thread.


Additional details:

I initialize some static delegate in some static class, to allow sending messages from business layer to GUI:

public static class Gui{
    private static PrintMethodDelegate _printMethod;
    public static void InitializeGuiInterface(PrintMethodDelegate printMethod){
        _printMethod = printMethod;
    }
    public static void Print(GuiMessage data) { _printMethod(data); }
}

Initialization (in the GUI thread):

Gui.InitializeGuiInterface(_messagesToUserHandler.PrintMessage);

Then in another (non-gui) thread, I use it:

Gui.Print(new GuiMessage(testDescription) { Foreground = new SolidColorBrush(someColor) });

while GuiMessage is:

public class GuiMessage {
    public string Msg { get; set; }

    private SolidColorBrush _foregroundBrush;
    public SolidColorBrush Foreground
    {
        get { return _foregroundBrush; }
        set { _foregroundBrush = value; }
    }
}
Tar
  • 8,529
  • 9
  • 56
  • 127
  • Have you tried to write the error message to google? It returns all answers we may give here. – I4V May 06 '13 at 06:55
  • 1
    I guess the short answer is that you can't. All GUI-related controls/artifacts should be created and handled on the UI thread. – Eben Roux May 06 '13 at 06:59
  • @EbenRoux: there is no way to do it ? no shared-memory (class / C#-API / attribue) facility to do that ? isn't that odd ? – Tar May 06 '13 at 08:14
  • 3
    You can create wpf resources in another thread if you [freeze](http://msdn.microsoft.com/en-us/library/ms750509.aspx) them, after that the element can be passed to yet another thread or the gui thread. – dowhilefor May 06 '13 at 09:11
  • @dowhilefor - you got it! it works! please post an answer so I can accept it. Thanks! – Tar May 06 '13 at 11:04
  • I didn't notice that this is related to WPF *sheepish grin*. I don't know WPF at all but glad you got your answer :) – Eben Roux May 07 '13 at 04:29

3 Answers3

8

You can create wpf resources in another thread if you freeze them, after that the element can be passed to yet another thread or the gui thread. Remember that a once frozen object can only be modified by making a copy and using that copy. You can't freeze objects that have Bindings or animations attached to it.

dowhilefor
  • 10,971
  • 3
  • 28
  • 45
  • That's it. I immediately freeze the `Brush` in the property setter. So it's not a multi-threading limitation... it was designed this way for this kind of objects (`Freezable`s) – Tar May 06 '13 at 13:40
1

You need to use a Delegate to safe invoke the control.

Use

Control.Invoke

or

Control.BeginInvoke

for this purpose.

private delegate void SetControlPropertyThreadSafeDelegate(Control control, string propertyName, object propertyValue);

public static void SetControlPropertyThreadSafe(Control control, string propertyName, object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate(SetControlPropertyThreadSafe), new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(propertyName, BindingFlags.SetProperty, null, control, new object[] { propertyValue });
  }
}

If you do not use the delegate to safe-invoke them, You will get the exception.

Check these links:

How to update the GUI from another thread in C#?enter link description here

enter link description here

Community
  • 1
  • 1
Carlos Landeras
  • 11,025
  • 11
  • 56
  • 82
  • I saw that, but this won't help me if the `Brush` was created in a non-GUI thread in the first place. I only solves a problem where you want to invoke on object created in the GUI thread, not vice-versa. – Tar May 06 '13 at 07:14
0

You should use the Dispatcher.

You can create a class that will hold a dispatcher created on a the main thread and inject it via your container to whatever class executed on a background thread that needs to interact with your main thread.

public interface IUiDispatcher
{
    Dispatcher Dispatcher { get; }
}

public class UiDispatcher : IUiDispatcher
{
    public UiDispatcher()
    {
        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA
            && !Thread.CurrentThread.IsBackground
            && !Thread.CurrentThread.IsThreadPoolThread)
        {
            this.Dispatcher = Dispatcher.CurrentDispatcher;
        }
        else
        {
            throw new InvalidOperationException("Ui Dispatcher must be created in UI thread");
        }
    }

    public Dispatcher Dispatcher { get; set; }
}

public class ExecutedOnABackgroundThread
{
    IUiDispatcher uidispatcher;

    public ExecutedOnABackgroundThread(IUiDispatcher uidispatcher)
    {
        this.uidispatcher = uidispatcher;
    }

    public void Method()
    {
        // Do something on the background thread...
        // ...

        // Now we need to do something on the UI
        this.uidispatcher.Dispatcher.BeginInvoke(new Action(delegate
        {
            // Do something
        }), null);
    }
}

Create the instance of UiDispatcher at a point your are sure your are on the UI thread, for example during the initialization of your application. Using your dependency injection container, ensure that only one instance of this class will be created and injected to any other class that requires it and use it to create/manipulate your UI components.

I picked the code to check if the constructor of UiDispatcher is executed in the main thread from this answer.

The thing is that you can't use on the UI thread something created on a different thread. So you need your background thread to delegate to the main UI thread whatever involves UI stuffs.

Community
  • 1
  • 1
Guillaume
  • 1,782
  • 1
  • 25
  • 42
  • As I said, "I know I can create this `SolidColorBrush` object in the GUI thread with `Dispatcher`, but that would complicate everything... I want to create it in the worker thread" – Tar May 06 '13 at 08:15
  • Yes I read it. But I'm not sure to get what's complicated. Especially if you just have to use a `this.dispatcher.BeginInvoke`. How can you use on the UI thread something created on the Background thread? [There's no other way than delegating the creation to the UI thread](http://www.google.com.sg/#output=search&sclient=psy-ab&q=The+calling+thread+cannot+access+this+object+because+a+different+thread+owns+it). – Guillaume May 06 '13 at 08:20
  • I googled that and was thinking that is ridiculous that it can't be done. You cannot copy data from some memory segment to some other memory segment ? clone the object ? it makes no sense. Why this limitation exists ? – Tar May 06 '13 at 10:42