\


 Monday, 02 March 2009
Náhrada ParametrizedThreadStart delegáta v Compact .Net Frameworku

Na fórech o Compact .Net Frameworku (CNF) se často objevují stesky,  že v CNF třída Thread nemá konstruktor, který by přijímal delegáta ParametrizedThreadStart. Metodě, na kterou ukazuje delegát ParametrizedThreadStart a která bude spuštěna v novém threadu, můžeme předat jeden argument typu object .

public delegate void ParametrizedThreadStartDelegate(Object obj);

 

Ty nářky jsou liché, protože můžeme předat do konstruktoru odkaz na instanční metodu bez argumentů ve vlastním objektu, který má ve svých proměnných nebo vlastnostech stavové informace, které použije instanční metoda poté, co je zavolána z metody Start threadu.

 

Pomocí anonymních metod či lambda výrazů se ale zbavíme nutnosti deklarovat vlastní třídu. Lambda výraz funguje jako adaptér, který převede metodu s jedním argumentem na metodu bez argumentů, kterou očekává konstruktor třídy Thread.

public partial class Form1 : Form   
    {   
        public Form1()   
        {   
            InitializeComponent();   
        }   
  
        private void Form1_Load(object sender, EventArgs e)   
        {   
            int myArg = 10;   
            Thread myThread = new Thread(() => MyThreadMethodWithArgument(myArg));   
            myThread.Start();   
        }   
  
  
        void MyThreadMethodWithArgument(Object obj)   
        {   
            Console.WriteLine(obj.ToString());   
        }   
  
           
    }  

 

Jestliže chcete použít syntaxi velmi podobnou použití delegáta ParametrizedThreadStartDelegate ve “velkém” .Net Frameworku, můžete si napsat vlastní třídu ParametrizedThreadStart, která umožňuje konverzi na delegáta ThreadStartDelegate a tedy opět funguje jako adaptér, který můžeme bez problémů předat do konstruktoru třídy Thread.

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ParametrizedThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            int myArg = 10;
            Thread myThread = new Thread(new ParameterizedThreadStart(MyThreadMethodWithArgument, myArg));
            myThread.Start();
        }


        void MyThreadMethodWithArgument(Object obj)
        {
            Console.WriteLine(obj.ToString());
        }

        
    }

    public delegate void ParametrizedThreadStartDelegate(Object obj);
    public class ParameterizedThreadStart
    {
        private ParametrizedThreadStartDelegate InnerDelegate { get; set; }
        private object Param { get; set; }

        public ParameterizedThreadStart (ParametrizedThreadStartDelegate del, object param)
        {
            InnerDelegate = del;
            Param = param;
        }

        public static implicit operator ThreadStart(ParameterizedThreadStart instance)
        {
            return (() => instance.InnerDelegate(instance.Param));
                     
            
        }
        

    }


}

 

Třída ParameterizedThreadStart vyžaduje, abyste do konstruktoru předali argument pro delegáta. Jestliže do konstruktoru argument ihned předat nechcete, ale chcete ve třídě Thread předat argument pro delegáta přetížené metodě Start, tak jako je tomu opět v NF, nezbývá než se na CNF uchýlit k extenzním metodám.

using System;
using System.Collections;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ParametrizedThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            
            Thread myThread = new Thread(new ParameterizedThreadStart(MyThreadMethodWithArgument));
            myThread.Start();
        }


        void MyThreadMethodWithArgument(Object obj)
        {
            Console.WriteLine(obj.ToString());
        }

        
    }

    public delegate void ParametrizedThreadStartDelegate(Object obj);
    public class ParameterizedThreadStart
    {
        public EventHandler<EventArgs> MyEvent;
        private ParametrizedThreadStartDelegate InnerDelegate { get; set; }
        private object Param { get; set; }
        private bool IsParamSetInConstructor { get; set; }

        public ParameterizedThreadStart(ParametrizedThreadStartDelegate del, object param)
        {
            InnerDelegate = del;
            Param = param;
            IsParamSetInConstructor = true;
        }

        public ParameterizedThreadStart(ParametrizedThreadStartDelegate del)
            : this(del, null)
        {
            IsParamSetInConstructor = false;
        }

        public static implicit operator ThreadStart(ParameterizedThreadStart instance)
        {

            return (() =>
                        {
                            
                            ThreadExtensions.SetThreadData();                                                        
                            var delArg = instance.IsParamSetInConstructor
                                             ? instance.Param
                                             : Thread.GetData(Thread.GetNamedDataSlot(ThreadExtensions.THREAD_DATA));




                            instance.InnerDelegate(delArg);
                        });
        }
    }
    

                                 


    public static class ThreadExtensions
    {
        public const string THREAD_DATA = "MethodData";
        private static Hashtable _threadDatahashTable = Hashtable.Synchronized(new Hashtable());
        
        public static void  Start (this Thread thread, object val)        
        {
            if (thread == null)
            {
                throw  new ArgumentNullException("thread");
            }
            
            _threadDatahashTable[thread.ManagedThreadId] = val;                                    
            thread.Start();            
        }

        internal static void SetThreadData()
        {
            object val = null;
            val = _threadDatahashTable[Thread.CurrentThread.ManagedThreadId];
            _threadDatahashTable.Remove(Thread.CurrentThread.ManagedThreadId);
            Thread.SetData(Thread.GetNamedDataSlot(THREAD_DATA), val);            
        }
    }
}

Operátor ThreadStart ve třídě ParametrizedThreadStartDelegate vrací složitější lambda výraz, ve kterém dojde k rozhodnutí, zda bude metodě, na kterou ukazuje InnerDelegate předán argument z konstruktoru, nebo argument, který byl předán extenzní metodě Start. Data specifická pro thread jsou v metodě SetThreadData vyzvednuta z objektu Hashtable a uložena v pojmenovaných datových slotech threadu. Extenzní metoda Start používá pro účely tohoto příkladu objekt Hashtable, protože pro Hashtable je  narozdíl od generické třídy Dictionary možné rychle získat její threadově bezpečnou (tedy z větší části threadově bezpečnou :-) ) verzi - Hashtable.Synchronized(new Hashtable()); a náš delegát ParametrizedThreadStartDelegate, přijímající typ object, si stejně na typovou bezpečnost moc nepotrpí. Tyto nevýhody by vás měly přesvědčit, že nejlepší, přímočaré a hacků prosté řešení jsem zmínil na začátku – vytvořte svoji vlastní třídu s instanční metodou a typovými vlastnostmi, které ponesou stavové informace. Další možností může být vytvoření vlastního wrapperu nad nativními API CreateThread a CreateFiber. :-) Chcete-li ale v CNF použít  ve třídě Thread delegáta ParametrizedThreadStartDelegate ve stylu NF, znáte nyní více způsobů, jak to provést.



Monday, 02 March 2009 13:48:05 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework