C# Cross-Thread communication

17,512

Solution 1

If You are using Windows Forms you can do something like this:

  1. In your Form add property

    private readonly System.Threading.SynchronizationContext context;
    public System.Threading.SynchronizationContext Context
    {
        get{ return this.context;}
    }
    
  2. In Your Form constructor set the property

    this.context= WindowsFormsSynchronizationContext.Current;
    
  3. Use this property to pass it to Your background worker as a constructor parameter. This way your worker will know about your GUI context. Create similar property inside Your background worker.

    private readonly System.Threading.SynchronizationContext context;
    public System.Threading.SynchronizationContext Context
    {
        get{ return this.context;}
    }
    
    public MyWorker(SynchronizationContext context)
    {
        this.context = context;
    }
    
  4. Change your Done() method:

    void Done()
    {
        this.Context.Post(new SendOrPostCallback(DoneSynchronized), null);
    }
    
    void DoneSynchronized(object state)
    {
        //place here code You now have in Done method.
    }
    
  5. In DoneSynchronized You should always be in Your GUI thread.

Solution 2

The Timer runs in the main thread (and will be late going off if the main thread is really busy). If for some reason you want jobDone() to be called from the main thread you could for instance use a BackGroundWorker object which is threaded internally and has some specific events that are called threadsafe with the main thread (allowing UI updates etc.)

Share:
17,512
losingsleeep
Author by

losingsleeep

Updated on June 29, 2022

Comments

  • losingsleeep
    losingsleeep almost 2 years

    in C#.NET , I've written the following simple background worker thread:

    public class MyBackgrounder
    {
        public delegate void dlgAlert();
        public dlgAlert Alert;
        public event EventHandler eventAlert;
        Thread trd;
    
        public void Start()
        {
            if (trd == null || trd.ThreadState == ThreadState.Aborted)
            {
                trd = new Thread(new ThreadStart(Do));
            }
            trd.IsBackground = true;
            trd.Priority = ThreadPriority.BelowNormal;
            trd.Start();
        }
    
        void Do()
        {
    
            Thread.Sleep(3000);
            Done();
        }
    
        void Done()
        {
            if (Alert != null)
                Alert();
            if (eventAlert != null)
                eventAlert(this, new EventArgs());
            Kill();
        }
    
        public void Kill()
        {
            if (trd != null)
                trd.Abort();
            trd = null;
        }
    }
    
    
    static class Program
    {
    
        [STAThread]
        static void Main()
        {
            MyBackgrounder bg = new MyBackgrounder();
            bg.eventAlert += new EventHandler(bg_eventAlert);
            bg.Alert = jobDone;
            bg.Start();
        }
    
        static void bg_eventAlert(object sender, EventArgs e)
        {
            // here, current thread's id has been changed
        }
    
        static void jobDone()
        { 
            // here, current thread's id has been changed
        }
    
    }
    

    It just waits for 3 seconds (does its job) and then raises an specified event or calls a delegate. there's no problem until here and everything works fine. But when i watch the 'Thread.CurrentThread.ManagedThreadId' , i see it's the background thread! maybe it's normal , but how can i prevent this behavior? if you test the 'System.Windows.Forms.Timer' component and handle its 'Tick' event , you can see that the 'Thread.CurrentThread.ManagedThreadId' has not been changed from main thread Id to anything else.

    what can i do?

    • Mare Infinitus
      Mare Infinitus almost 12 years
      There is no problem in your code and you just want to understand, right? The backgroundworker is in another thread. Where do you watch the thread id?
    • Kuba Wyrostek
      Kuba Wyrostek almost 12 years
      System.Windows.Forms.Timer uses internal Windows WM_TIMER event and simply counts number of ticks, then raises the .NET event. Therefore it can work in main thread of process. The background worker you wrote works in background thread (explicitly by creating new Thread()), overwise the Thread.Sleep(3000) would suspend all your application. What is the problem with this running in background thread exactly?
    • losingsleeep
      losingsleeep almost 12 years
      @MareInfinitus , when the background worker object raises its event or calls the specified method, there, i see the changed thread id. it means that my background worker has called those methods.
    • Mare Infinitus
      Mare Infinitus almost 12 years
      Thats right, the called methods are not in the background worker thread but in the thread that provides those callbacks.
    • losingsleeep
      losingsleeep almost 12 years
      @KubaWyrostek, Thanks. I want to solve (change) the background worker behavior , so I'l be safe later interacting UI elements. it seems that i must write the Backgrounder using native Win APIs just like .NET's Timer, not? but how it works in an independent class without using threads!?
    • Kuba Wyrostek
      Kuba Wyrostek almost 12 years
      There is no need to do so. You can interact with UI from background thread as long as you stick to Microsoft's rules. :-) Please consult this MSDN article: msdn.microsoft.com/en-us/library/ms171728.aspx Also I guess it wouldn't be possible to use Forms.Timer in non-UI thread as it is UI-control.
    • losingsleeep
      losingsleeep almost 12 years
      @KubaWyrostek, I've worked the solution you mentioned by MSDN link before , but i don't want to write more code lines (safety checking code) for every control on my forms and user controls. i just wanted to solve the problem once. I think i must get help from my UI Form to obtain a timer tick using its Timer component. Thank you anyway.
    • Kuba Wyrostek
      Kuba Wyrostek almost 12 years
      You don't need to write a lot safety checking code actually. All you need to to is give your MyBackgrounder class a reference to Form and always wrap Alert/eventAlert delegates in this Form's Invoke. There is no reason why you should avoid executing the delegate in UI's thread even if the delegate does not change anything in UI actually. :-)
    • losingsleeep
      losingsleeep almost 12 years
      @KubaWyrostek , The MyBackgrounder class and its caller are both in one layer separate from UI layer. this is the scenario: A UI Form (layer1) uses a non-UI class (layer2) to do something. layer2 itself needs a timer to handle the specified timeout during its job. because layer1 has a reference to layer2, i can't give layer2 a reference to layer1 again.
    • Kuba Wyrostek
      Kuba Wyrostek almost 12 years
      OK, I've posted another solution in answers here. Please review if this would fit your needs.
  • Kuba Wyrostek
    Kuba Wyrostek almost 12 years
    Nice solution. Please note that BackgroundWorker's Context property should be readonly or have its setter synchronized.
  • Grzegorz W
    Grzegorz W almost 12 years
    @KubaWyrostek True that! I changed the properties.
  • Charleh
    Charleh almost 12 years
    Any remaining threads are stopped and do not complete - I guess these all throw ThreadAbortException
  • losingsleeep
    losingsleeep almost 12 years
    @GrzegorzWilczura,Thanks for your help, but if i decide to have a reference to "System.Windows.Forms" in my underlying layer , don't you think it's better to use the "Timer" component instead of "WindowsFormsSynchronizationContext"? i've tested it and it works fine , cause the top running application is a windows form. i think it's more easier to use.
  • losingsleeep
    losingsleeep almost 12 years
    @GrzegorzWilczura please take a look at my own answer. thanks a lot for your help my friend.
  • Grzegorz W
    Grzegorz W almost 12 years
    @losingsleeep SynchronizationContext is in mscorlib.dll so I think You don't need to reference System.Windows.Forms.dll in background worker project if it's implemented that way.
  • Grzegorz W
    Grzegorz W almost 12 years
    Timer tick is blocking the main thread. There isn't something like non-blocking code execution without different thread in one application domain.
  • losingsleeep
    losingsleeep almost 12 years
    Yes Grzegorz. In this example, except the Timer component everything is blocking, and it's not a problem for me. i just wanted use a timeout without creating another thread.