Pokazywanie postów oznaczonych etykietą func. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą func. Pokaż wszystkie posty

poniedziałek, 21 kwietnia 2014

[C#|Visual Studio] C#: covariance, contravariance

Kiedy metoda korzystające z generycznych parametrów jest kompilowana przez JIT compiler, to CLR bierze jej IL i podmienia argumenty, a następnie tworzy kod specyficzny dla metody operującej na konkretnym typie. W związku z tym CLR wygeneruje osobny kod dla każdej kombinacji metod / parametrów, co nazywane jest code explosion. W związku z tym, konwersje pomiędzy typami o generycznych parametrach wydawać by się mogły technicznie trudne do realizacji. Wraz z C# 4.0 możemy konwertować w dwie strony za pomocą mechanizmów kowariancji i kontrawariancji.

Kowariancja
Typ generyczny z parametrem a może być rzutowany do innego typu z parametrem b jeżeli parametr b jest typem bazowym dla typu a. Słowo kluczowe C# dla kowariancji to out.

internal interface ICovariantable<out T> where T: new ()
{
    T GetValue();
}

public class MyCovariant<T> : ICovariantable<T> where T: new()
{
    public T GetValue()
    {
        return new T();
    }
}

public class SampleClass: object 
{
    public SampleClass()
    {
        
    }
}

Kontrawariancja
Typ generyczny z parametrem a może być rzutowany do innego typu z parametrem b jeżeli parametr a jest typem bazowym dla typu b. Słowo kluczowe C# dla kowariancji to in.

internal interface IContravariantable<in T>
{
    void SetValue(T param);
}

public class MyContravariant<T> : IContravariantable<T>
{
    public void SetValue(T param)
    {
        Console.WriteLine("value was set");
    }
}


static void Main(string[] args)
{
    //Covariance

    var instance1 = new MyCovariant<object>();
    var instance2 = new MyCovariant<SampleClass>();

    ICovariantable<object>[] instances = {instance1, instance2};

    //Contravariance

    var instance3 = new MyContravariant<object>();
    var instance4 = new MyContravariant<SampleClass>();

    IContravariantable<SampleClass>[] instances2 = { instance3, instance4 };

    Console.ReadKey();
}

Jeżeli nie wyspecyfikujemy parametru out dla pierwszego interfejsu, to kompilator zaprotestuje przeciwko umieszczeniu w tablicy drugiego elementu (instance2), natomiast jeżeli nie podamy in, to niedozwolone będzie umieszczenie w drugiej tablicy obiektu instance3.

Często wykorzystywane delegaty Func i Action wykorzystują kowariancję i kontrawariację: parametry wejściowe są typu in, natomiast wartości zwracane (w przypadku Func) są oznaczone jako out.

sobota, 9 lutego 2013

[C#|Visual Studio] Threading: Thread Pool

Pula wątków rozwiązuje dwa dosyć poważne problemy związane ze współbieżnością w .NET:
  • każdy wątek domyślnie wykorzystuje około 1MB pamięci
  • utworzenie nowego wątku wraz z organizacją pamięci dla niego trwa setki milisekund
Dzięki puli wątków zyskujemy automatyczne współdzielenie i odzyskiwanie już pracujących wątków. Ponadto kontrolowana jest maksymalna ilość wątków typu "worker" jakie mogą pracować w danej chwili. Zbyt duże ilości aktywnych wątków mogą zaburzać pracę systemu operacyjnego, co staje się niewygodne dla użytkownika. Gdy zastosuje się Thread Pool, to w przypadku przekroczenia maksymalnej liczby wątków, nowe wątki są kolejkowane i uruchamiane dopiero w momencie, gdy któreś z aktywnych wątków skończą swoją pracę.

Przed rozpoczęciem pracy z pulą wątków należy pamiętać, że będą one zawsze wątkami typu "background".

Dostęp do puli wątków można uzyskać na kilka sposobów:

Pula wątków poprzez Task Parallel Library (TPL)

TPL dostarcza specjalną klasę Task, która dostępna jest pod dwoma postaciami: generyczną i niegeneryczną. Wątek z Thread Pool można wystartować w poniższy sposób:

public static void Run()
{
    Task.Factory.StartNew(NonGenericTask);
}

public static void NonGenericTask()
{
    Console.WriteLine("This is thread from thread pool");
}

lub w sposób generyczny, gdzie możemy zwracać wartość z wątku.

public static void Run()
{
    Task<string> downloader = Task.Factory.StartNew(
        () => GenericTask("http://www.wisla.krakow.pl"));
    Console.WriteLine("Some operations...");
    var result = downloader.Result;
    Console.WriteLine(result);
}

public static string GenericTask(string uri)
{
    Console.WriteLine("This is thread from thread pool");
    using (var wc = new System.Net.WebClient())
        return wc.DownloadString(uri);
}

Warto pamiętać, że w momencie, gdy odwołujemy się do property Result, wątek główny zostanie zawieszony do momentu wywołania wątku z puli.

TPL został wprowadzony w .NET 4.0. W starszych wersjach zachodzi konieczność korzystania z puli wątków w nieco inny sposób.

Pula wątków poprzez klasę ThreadPool

Tutaj mamy do dyspozycji klasę ThreadPool, gdzie wywołując wątek możemy przez parametr podać stan (jako typ object).

public static void Run()
{
    Console.WriteLine("Without TPL");
    ThreadPool.QueueUserWorkItem(QueueUserWorkItemFcn, 
        DateTime.Today.ToShortDateString());
}

public static void QueueUserWorkItemFcn(object dateTime)
{
    Console.WriteLine("This is thread from thread pool, at {0}", dateTime);
}

Jeżeli chcemy zwracać obiekty z wątku nie używając TPL, należy wykorzystać tzw. asynchronous delegates.

public static void Run()
{
    Console.WriteLine("Asynchronous delegate");
    Func<string, string> func = new Func<string, string>(AsynchronousDelegate);
    IAsyncResult value = func.BeginInvoke("this is some text", null, null);
    Console.WriteLine("Some operations...");
    var str = func.EndInvoke(value);
    Console.WriteLine(str);
}

public static string AsynchronousDelegate(string letters)
{
    Console.WriteLine("This is thread from thread pool");
    IEnumerable<char> rev = letters.Reverse();
    string result = "";
    foreach (var @char in rev)
    {
        result += @char;
    }
    return result;
}

Operacja EndInvoke czeka aż asynchroniczny delegat wykona swoje zadanie, zawieszając główny wątek, odbiera rezultat przetwarzania, a także "przerzuca" wyjątek z wątku w tle do wątku głównego.

Wywołując BeginInvoke można jako drugi parametr przekazać callback, który wykona się po zakończeniu pracy wątku. Callback jako parametr przyjmuje typ IAsyncResult.

public static void Run()
{
    var method = new Func<string, string>(AsynchronousDelegate);
    method.BeginInvoke("My text", Callback, method);
}

public static void Callback(IAsyncResult result)
{
    var target = (Func<string, string>)result.AsyncState;
    string res = target.EndInvoke(result);
    Console.WriteLine(res);
}