\


 Sunday, July 2, 2006
Řešení programátorské hádanky z 24.6.2006 - ThreadStartDelegate

Jak bylo patrné ze zadání "hádanky", snažil jsem se, abyste mi napsali, jaké postupy používáte pro předání argumentů a získání návratové hodnoty z metody, když jste omezeni signaturou delegátu ThreadStartDelegate. Třída Thread dokáže pracovat pouze s metodami bez návratových hodnot (void) a také:

a) Metoda nesmí mít žádné argumenty  - delegát ThreadStartDelegate

b) Metody přijímající jeden argument typu object - delegát ParameterizedThreadStart (pouze v  .Net Frameworku 2.0).

Jak jste se zmínili v komentářích - když chceme předat argumenty kódu vykonávanému v samostatném threadu, můžeme použít třídy s vlastním stavem a návratovou hodnotu získat třeba tak, že si zaregistrujeme "callback" funkci.

Mně by se ale líbilo, kdybych měl  jen jednu "stavovou" třídu s argumenty pro metody s jakoukoli signaturou a tedy společnou pro všechny rozdílné typy delegátů a hlavně chci objekt z této třídy přímo předat do konstruktoru třídy Thread, aby se můj kód nijak nelišíl od přímého použití delegátů ThreadStart a ParameterizedThreadStart.  Proto jsem v komentářích mluvil o eleganci zvoleného řešení :)

    class ThreadStartDelegateWrapper
    {
        #region Private members
        private Delegate m_innerDelegate;
        private object[] m_methodParameters;
        private object m_returnValue;
        #endregion Private members

        #region Construtors
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="del">Delegát ukazující na metodu, která poběží v jiném threadu</param>
        public ThreadStartDelegateWrapper(Delegate del)
        {
            if (del == null)
            {
                throw new ArgumentNullException("del");
            }
            m_innerDelegate= del;
        }

        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="del">Delegát ukazující na metodu, která poběží v jiném threadu</param>
        ///<param name="methodParameters">Argumenty metody</param>
        public ThreadStartDelegateWrapper(Delegate del, params object[] methodParameters) : this(del)
        {
            m_methodParameters = methodParameters;
        }

        #endregion Construtors

        #region Public properties
        /// <summary>
        /// Argumenty metody
        /// </summary>
        public object[] MethodParameters
        {
            get
            {
                return m_methodParameters;
            }
            set
            {
                m_methodParameters = value;
            }
        }

        /// <summary>
        /// Návratová hodnota metody
        /// </summary>
        public object ReturnValue
        {
            get
            {
                return m_returnValue;
            }
        }
        #endregion Public properties

        #region Operators
        /// <summary>
        /// Implicitní konverzní operátor, který instanci třídy <see cref="ThreadStartDelegateWrapper"/> převede na delegáta <see cref="ThreadStartDelegate"/>
        /// </summary>
        /// <param name="originalWrapper">Instance třídy <see cref="ThreadStartDelegateWrapper"/></param>
        /// <returns>Instanci delegáta <see cref="ThreadStartDelegate"/>, který ukazuje na metodu zapouzdřující zavolání jakéhokoli delegáta</returns>
        public static implicit operator ThreadStart(ThreadStartDelegateWrapper originalWrapper)
        {
            if (originalWrapper == null)
            {
                throw new ArgumentNullException("originalWrapper");
            }

            return new ThreadStart(originalWrapper.InvokeDelegate);
        }
        #endregion Operators

        #region private methods
        //Metoda, jejíž signatura odpovídá signatuře deklarované delegátem ThreadStartDelegate
        private void InvokeDelegate()
        {
            m_returnValue = m_innerDelegate.DynamicInvoke(m_methodParameters);
        }
        #endregion private methods
    }
 

Třídě ThreadStartDelegateWrapper můžete do konstruktoru předat odkaz na jakéhokoli delegáta a také můžete předat všechny potřebné argumenty metodě, na kterou delegát ukazuje (argument methodParameters v konstruktoru). Předané argumenty můžete kdykoli upravit, protože jsou zveřejněny ve vlastnosti MethodParameters.

Abych mohl svoji třídu použít kdekoli je očekáván delegát ThreadStart, napsal jsem implicitní konverzní operátor třídy ThreadStartDelegateWrapper na delegát ThreadStartDelegate.

Návratovou hodnotu metody vykonané v jiném threadu nalezneme ve vlastnosti ReturnValue - nic nám také nebrání přidat událost, ve které získanou návratovou hodnotu budeme sami aktivně distribuovat všem zájemcům.

A tady je jednoduchý ukázkový kód.

    class Test
    {
        public delegate string TestDelegate(string message);

        public string WriteMessage(string message)
        {
            Console.WriteLine(message);
            return "OK";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Test myTestClass = new Test();
            Test.TestDelegate myDelegate = myTestClass.WriteMessage;

            ThreadStartDelegateWrapper wrapper = new ThreadStartDelegateWrapper(myDelegate, new object[] { "Hello world" });
            Thread myThread = new Thread(wrapper);
            myThread.Start();
            myThread.Join();

            Console.WriteLine(wrapper.ReturnValue);
            Console.Read();
            
        }
    }

Je jednoduché rozšířit třídu ThreadStartDelegateWrapper o podporu dalších delegátů v .NET Frameworku (např. WaitCallback), takže na metody s různými signaturami mohou ukazovat typičtí delegáti v .Net Frameworku  s využitím jedné jediné třídy fungující jako obecný adaptér mezi naším kódem a kódem v .Net Frameworku.



Sunday, July 2, 2006 2:40:08 PM (Central Europe Standard Time, UTC+01:00)       
Comments [3]  .NET Framework | Programátorské hádanky


Sunday, July 2, 2006 8:42:14 PM (Central Europe Standard Time, UTC+01:00)
tak koukam, ze jsem se trefil ;-)
Sunday, July 2, 2006 9:23:03 PM (Central Europe Standard Time, UTC+01:00)
Skoro Michale, jen ti tam chybel kompletne ten konverzni operator ;)
Monday, July 3, 2006 9:04:23 AM (Central Europe Standard Time, UTC+01:00)
To mas pravdu. Vlastni operatory jsem prestal pouzivat pote, co jsem zjistil, ze je z VB.NET (.NET 1.1) nemohu pouzit.

proste je to odmitne brat na vedomi jak VS.NET, tak kompiler, i kdyz jsou spravne napsane. (samozrejme napsane v knihovne v C#)
Comments are closed.