0

I have a form and some control on it :

public class Tester : Form
{
    public Label Demo;

    public Label GetDemo()
    {
        return Demo.Text;
    }
}

Also I have some static class :

public static bool Delay(Func<bool> condition)
{
    bool result = false;
    AutoResetEvent e = new AutoResetEvent(false);

    Timer t = new Timer(delegate {
        if (result = condition()) e.Set(); // wait until control property has needed value
    }, e, 0, 1000);

    e.WaitOne();
    t.Dispose();

    return result;
}

At some point control creates new thread and calls our static method :

ThreadPool.QueueUserWorkItem(delegate {
    if (Delay(() => GetDemo() == "X")) MessageBox.Show("X");
}, null);

Of course, this will cause an exception because GetDemo will be passed to Delay and will be called in a new thread as a delegate.

Of course, it is possible to solve it by using Invoke to call our static method :

ThreadPool.QueueUserWorkItem(delegate {
    Invoke((MethodInvoker) delegate {
        if (Delay(() => GetDemo() == "X")) MessageBox.Show("OK");
    }
}, null);

Unfortunately, i am not allowed to change call of Delay, i can change only its implementation.

Question :

1) what needs to be changed INSIDE static method Delay so that condition() would executed GetDemo in its native thread without exceptions?

2) is it possible to do something like this inside Delay?

SynchronizationContext.Dispatcher((Action) delegate {  
    if (condition()) e.Set();
});
Anonymous
  • 1,823
  • 2
  • 35
  • 74
  • Rather than constantly checking whether or not a condition is met whatever code would cause that condition to be met should fire an event, which this code could then add an event handler to. That will make the code far simpler, and it will make the program asynchronous, thus not blocking the UI thread. – Servy Oct 02 '14 at 17:05
  • result is always false. Did you mean to assign it somewhere? – Dave Mackersie Oct 02 '14 at 17:07
  • @Dave Mackersie : sorry, corrected, yes it has an assignment – Anonymous Oct 02 '14 at 17:08
  • @Servy : could you please clarify what exactly part of the code should fire an event? as far as i can see condition() is a delegate and it calls original method GetDemo() which looks like kind of event model but this does not work because any event will be calling GetDemo() from the wrong thread. Did i miss something? – Anonymous Oct 02 '14 at 17:13
  • Clearly the calling code is at fault here. It doesn't just stop at your Delay() method, displaying a message box on a thread pool thread is nonsensical as well. Fixing this inside Delay() just creates a gothic mess. This is not your bug, pass it on. – Hans Passant Oct 02 '14 at 17:15
  • @Art You'll either need to have the event handler, or the code firing the event, marshal to the UI thread, yes. You can use the `SynchronizationContext` context to do this, like any other time you need to marshal to the UI thread ever. – Servy Oct 02 '14 at 17:16
  • @HansPassant, the message box should have it's own message pump, so I think it will work to call it on a non-UI thread. Maybe not a good design, though. – Dave Mackersie Oct 02 '14 at 17:23

1 Answers1

-1

This solution assumes that there is someplace else in your code that you can receive an earlier call on the UI thread, to save off a copy of the UI SynchronizationContext. That may not be the case, in which case my proposed solution won't work.

// Assign this using SynchronizationContext.Current from a call made on the UI thread.
private static SynchronizationContext uiSynchronizationContext;

public static bool Delay(Func<bool> condition)
{
    bool result = false;
    AutoResetEvent e = new AutoResetEvent(false);

    Timer t = new Timer(delegate 
    {
        uiSynchronizationContext.Send(s => result = condition(), null);

        if (result)
            e.Set(); // wait until control property has needed value
    }, e, 0, 1000);

    e.WaitOne();
    t.Dispose();

    return result;
}
Dave Mackersie
  • 1,033
  • 10
  • 14
  • 2
    Congratulations, you just deadlocked your application. – Servy Oct 02 '14 at 17:26
  • I don't think so. But looking at this again, I'm using the wrong synchronization context. Delay is called from a worker thread, not from the UI thread. I need to somehow get the UI Synchronization context. – Dave Mackersie Oct 02 '14 at 17:32
  • Had you *succeeded* in marshaling to the UI thread you'd have deadlocked the program. Of course, if you failed then, well, you failed to answer the question. – Servy Oct 02 '14 at 17:35
  • OK, I've edited my result to use the UI synchronization context. Now, where do you see the deadlock? The timer thread marshals a call on to the UI thread, which should immediately return. Do you see the UI thread being blocked somewhere? – Dave Mackersie Oct 02 '14 at 17:41
  • *You* were blocking the UI thread in your original revision. After your edits you're simply failing to solve the problem as this method isn't marshaling to the UI thread without refactoring how the method is called. – Servy Oct 02 '14 at 17:45
  • What blocking call was I making on the UI thread? My original revision didn't even have any code on the UI thread - everything I wrote was in a worker thread or the Timer thread. Now, the code within the SynchronizationContext.Send callback should be called from the UI thread, no? Do you have a different understanding of how SynchronizationContext works? – Dave Mackersie Oct 02 '14 at 17:57
  • You were getting the current context (meaning the method needs to be running in the UI thread), blocking in that method call, and then sending messages to that context from another thread. That results in a deadlock. – Servy Oct 02 '14 at 18:02
  • @Servy - I think I may understand the problem - I was assuming System.Timers.Timer, which uses a worker thread. Perhaps you were assuming System.Windows.Forms.Timer, which uses the UI thread? – Dave Mackersie Oct 02 '14 at 18:02
  • I was making no such assumption. Regardless, it's not productive to discuss why, specifically, your earlier revision didn't work. – Servy Oct 02 '14 at 18:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62358/discussion-between-dave-mackersie-and-servy). – Dave Mackersie Oct 02 '14 at 18:13