7

I'm working with web services so its necessary for me to extend session length/reconnect and get large datasets back etc. Sometimes this can be lengthy so I wanted it in an separate thread that updates the UI asynchronously.

I can't seem to get my head around using the synchronizationContext to invoke a method on my UI thread. I have it whereby I have passed my UIThread context to my thread and now I want to update some labels etc on UI Thread. I've read tons of posts but none seem to explain how to simply pass some parameters back to a method, or maybe they do but i'm too tired/stupid to have seen it.

//On main UI Thread

public void updateConnStatus(string conn_name, bool connected)
{
        switch (conn_name)
        {
            case "Conn" : if (connected == true){ //do something} break;

//on separate Thread

uiContext.Post( //something to do with delegates in here that eludes me );

if someone could simply explain how I link the sendOrPostCallBack to the original method I would be very grateful.

Thanks

Edit:

I managed to get the code to run and try to fire the event, it populates my custom eventArgs okay but either its saying that updateUIConnStatus has not been instantiated, needs more investigation :o

public void updateUIThread(string conn, bool connected)
    {
       uiContext.Post(new SendOrPostCallback((o) => { updateConnStatus(this, new MyEventArgs<String, Boolean>(conn, connected)); }), null);
    }

public class MyEventArgs<T, U> : EventArgs
    {
        private T _val1; private U _val2;
        public  MyEventArgs(T value1, U value2) { _val1 = value1; _val2 = value2; }
        public T val1 { get { return _val1;} }
        public U val2 { get {return _val2;} }
    }

public event EventHandler<MyEventArgs<String, Boolean>> updateConnStatus = Delegate {};

//on UI Thread Now

 public void updateConnStatus(object sender, MyEventArgs<String,Boolean> e)
    {
        switch (e.val1)
        {
            case "Conn1" :
                if (e.val2 == true)
                {
Peter Lea
  • 1,731
  • 2
  • 15
  • 24

2 Answers2

21

You need a delegate of type SendOrPostCallback. Which is pretty awkward, it only takes a single argument of type object. You definitely ought to look at the Task<> class available in .NET 4 to make this easier. Or use a lambda, like this:

        string conn_name = "foo";
        uiContext.Post(new SendOrPostCallback((o) => {
            updateConnStatus(conn_name, true);
        }), null);

The code between the { braces } executes on the UI thread.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Hi Hans, I had originally tried something just like but I receive the ' The name 'updateConnStatus' does not exist in the current context' as the code is in a threadwrapper class. I've just tried sticking an eventhandler on there which seems to have removed the error so i'll test it and get back! :) – Peter Lea Aug 19 '12 at 19:46
  • Well, use the correct object reference if this method lives in another class. Or sure, an event. – Hans Passant Aug 19 '12 at 19:50
  • No, I wasn't using it correctly :( I have working examples of using delegates with AddressOf & invoke etc in vb.net but i'm new to c# and it looked like SynchonisationContext was a nicer method, but I just can't seem generate the event handler correctly or reference to the main UI method :/ – Peter Lea Aug 19 '12 at 20:53
  • Well, make small steps. Do *not* use a "thread wrapper", just put it in the same class as your updateConnStatus method. Learning OOP and unlearning vb.net takes a while. – Hans Passant Aug 19 '12 at 21:00
  • Well I managed to get the code to run by creating a custom EventArgs Class using generics. It just seems that there's just an issue with the event. – Peter Lea Aug 19 '12 at 22:10
  • Pretty high odds here that you dove in head first and never noticed the BackgroundWorker class on the way down. Which was completely designed to keep you out of trouble like this. Hacking SynchronizationContext is an advanced art, never actually needed. – Hans Passant Aug 19 '12 at 22:15
  • I edited main post. My main issue is that I need to pass some objects by reference to the thread, this is why I choose to use a threadwrapper class so it could manipulate the ref objects. – Peter Lea Aug 19 '12 at 22:22
  • I've accepted your answer Hans as it got me half way there, thanks for your help. I created a new post that more specifically details my current problem. – Peter Lea Aug 20 '12 at 11:38
7

Typically you are creating instances of your types (e.g. ViewModels) on the UI thread, so you can just save the SynchronizationContext or the TaskScheduler (preferable IMHO) to a private field and then compare it when needed...

private readonly SynchronizationContext _syncContext = SynchronizationContext.Current;
private readonly TaskScheduler _scheduler = TaskScheduler.Current;

void OnSomeEvent(object sender, EventArgs e)
{
    if (_syncContext != SynchronizationContext.Current)
    {
        // Use Send if you need to get something done as soon as possible.
        // We'll be polite by using Post to wait our turn in the queue.
        _syncContext.Post(o => DoSomething(), null);
        return;
    }
    // Call directly if we are already on the UI thread
    DoSomething();
}

void OnSomeOtherEvent(object sender, MyEventArgs e)
{
    var arg1 = e.Arg1; // "Hello "
    var arg2 = e.Arg2; // {"World", "!"};

    // Process args in the background, and then show the result to the user...
    // NOTE: We don't even need to check the context because we are passing
    // the appropriate scheduler to the continuation that shows a MessageBox.

    Task<string>.Factory.StartNew(() => ReturnSomething(arg1, arg2))
        .ContinueWith(t => MessageBox.Show(t.Result), _scheduler);
}

void DoSomething() { MessageBox.Show("Hello World!"); }

string ReturnSomething(string s, IEnumerable<string> list)
{
    return s + list.Aggregate((c, n) => c + n);
}
Ben Stabile
  • 71
  • 1
  • 2