1

I'm writing a connection handler (a dialog to request username and password). The code is a handler that shows a dialog. This code could be called from a thread, so I need to Invoke() if InvokeRequired.

In a ideal situation I can initialize then handler with a Control to do InvokeRequired, but sometimes the Control could be null. Is it possible? How could I implement the code? Is the following correct?

public class GuiCredentialsHandler
{
    // control used to invoke if needed
    private static Control mInvokeControl;

    /// <summary>
    /// Initialize a GetCredentials handler for current process.
    /// This method should be always called from the main thread, for
    /// a correctly handling for invokes (when the handler is called
    /// from a thread).
    /// </summary>
    /// <param name="parentControl">Application top form. 
    /// Can be null if unknown</param>
    public static void Initialize(Control parentControl)
    {
        if (parentControl != null)
        {
            mInvokeControl = parentControl;
        }
        else
        {
            mInvokeControl = new Control();
            // force to create window handle
            mInvokeControl.CreateControl();
        }
    }

    public static Credentials GetCredentials()
    {
        if (mInvokeControl.InvokeRequired)
        {
            return mInvokeControl.Invoke(
                new GetCredentialsDelegate(DoGetCredentials), null) 
                as Credentials;
        }
        else
        {
            return DoGetCredentials();
        }
    }

    private static Credentials DoGetCredentials()
    {
        // the code stuff goes here
    }

}

My questions are:

  1. What happens if I pass a null control to the InitializeMethod()
  2. If the Initialize() method is executed in the UIThread, will the code work later?
  3. What is the recommended pattern if you havn't got any control to test InvokeRequired?

Thanks in advance


EDIT: Doing some tests, I have realized that if I pass null to Initialize(), the control is not running in the UI thread so the InvokeRequired seems to return false. Always. So my question is, how can I perform a real (fiable) Invoke when I have no control?


EDIT2: Doing mInvokeControl.CreateControl() fixs the issue.

Daniel Peñalba
  • 30,507
  • 32
  • 137
  • 219
  • If you're creating a dialog box / form to retrieve the login credentials, you should be able to use that as your invoke target. Otherwise, where are the credentials coming from?? – 3Dave Jul 15 '11 at 14:54
  • @David Lively: Yes, but I'm creating the form in the call DoGetCredentials – Daniel Peñalba Jul 15 '11 at 14:55

2 Answers2

3

Implement ISynchronizeInvoke on that class instead. Here is an example:

public class GuiCredentialsHandler : ISynchronizeInvoke
{
        //....

        private readonly System.Threading.SynchronizationContext _currentContext = System.Threading.SynchronizationContext.Current;

        private readonly System.Threading.Thread _mainThread = System.Threading.Thread.CurrentThread;

        private readonly object _invokeLocker = new object();
        //....


        #region ISynchronizeInvoke Members

        public bool InvokeRequired
        {
            get
            {
                return System.Threading.Thread.CurrentThread.ManagedThreadId != this._mainThread.ManagedThreadId;
            }
        }

        /// <summary>
        /// This method is not supported!
        /// </summary>
        /// <param name="method"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        [Obsolete("This method is not supported!", true)]
        public IAsyncResult BeginInvoke(Delegate method, object[] args)
        {
            throw new NotSupportedException("The method or operation is not implemented.");
        }

        /// <summary>
        /// This method is not supported!
        /// </summary>
        /// <param name="method"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        [Obsolete("This method is not supported!", true)]
        public object EndInvoke(IAsyncResult result)
        {
            throw new NotSupportedException("The method or operation is not implemented.");
        }

        public object Invoke(Delegate method, object[] args)
        {
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }

            lock (_invokeLocker)
            {
                object objectToGet = null;

                SendOrPostCallback invoker = new SendOrPostCallback(
                delegate(object data)
                {
                    objectToGet = method.DynamicInvoke(args);
                });

                _currentContext.Send(new SendOrPostCallback(invoker), method.Target);

                return objectToGet;
            }
        }

        public object Invoke(Delegate method)
        {
            return Invoke(method, null);
        }

        #endregion//ISynchronizeInvoke Members

}

Note: Because of the class implementation it uses System.Threading.SynchronizationContext.Current so you can use it in WindowsForms or wpf but not a Console application because the System.Threading.SynchronizationContext.Current is null.

Jalal Said
  • 15,906
  • 7
  • 45
  • 68
  • Thanks for your answer. What about creating an instance of this ISynchronizeInvoke class, if you're not in the UI thread? For example if you create the class from a Windows Explorer shell extension thread. – Daniel Peñalba Jul 15 '11 at 15:14
  • @Daniel: you are welcome, `ISynchronizeInvoke` is an interface, you can't create instance of it. you can create instance from the class that implements it... however if you are not in the UI thread and call `Invoke` it will invoke the method on the UI thread even if you are in a different thread. if you are not using windows forms or wpf, you have to implement your own `SynchronizationContext` to marshal the execution on the thread you want. – Jalal Said Jul 15 '11 at 20:48
1

A simple solution is to create an invisible control in the main thread on which your worker threads can call Invoke.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I tried this, but you need to call `control.CreateControl()`. Otherwise it does not work. I reviewd the `Control` class with reflector and `InvokeRequired` compares current thread agains the thread that created the control handle. If you don't call `CreateControl()`, the handle is never created and always return false. – Daniel Peñalba Jul 15 '11 at 15:25
  • @Daniel That makes perfect sense. The whole reasoning behind `Invoke` is to meet the Win32 requirement that functions receiving window have to me made on the thread that created the window. You have to make sure that the handle is create on the UI thread at startup of your app. – David Heffernan Jul 15 '11 at 15:27