C#: Работа с потоками.

Краткая шпаргалка — часть 1.

Платформа .NET предложила множество способов пос­троения программного обеспечения, которое может выполнять сложные операции по уникальным путям выполнения, с намного меньшими сложностями. Поток определен как путь выполнения внутри исполняемого прило­жения. Первичный поток сборки (создаваемый CLR-средой при выполнении Main()) в любое время может создавать вторичные потоки. За счет создания дополнительных потоков можно строить бо­лее отзывчивые (но не обязательно быстрее выполняемые на одноядерных машинах) приложения.

Создание параллельного потока при помощи делегата.


Создадим тип делегата для демонстрации.

delegate int BinaryOp(int x, int y);

Создадим функцию для типа делегата.

 public static int Add(int x, int y)
        {
            Console.WriteLine("Go thread ID:{0}!!",Thread.CurrentThread.ManagedThreadId);
            //демонстрируем бурную деятельнсть
            Thread.Sleep(1000);
            //сообщить другому потоку о завершении
            waitHandle.Set();
            return x + y;
        }

Инициируем делегат:

	BinaryOp b = new BinaryOp(Add);
    //Вызываем синхронно
    int res = b(x, y);
    //или так
    res = b.Invoke(x, y);

Теперь сделаем асинхронный вызов, т. е. создадим отдельный поток для вызова:

 	IAsyncResult aRes = b.BeginInvoke(x, y, null, null);
    //Ждем завершения потока тупо
    if (!aRes.IsCompleted)
    {
         //Чего нибудь делаем или тупо ждем
          Console.WriteLine("Подожем, твою мать/n");
    }
    //Дождались - асинхронный вызов функции Add
    res = b.EndInvoke(aRes);
    Console.WriteLine("Псевдоасинхронный вызов функции Add результат = {0}", res);

На самом деле вызов EndEnvoke тормозит текущий поток до завершения потока функции Add.
Поэтому я бы назвал вызов псевдоасинхронным, несмотря на параллельный поток. Правильно использовать функцию обратного вызова при вызове BeginInvoke.

	aRes = b.BeginInvoke(x,y, AddComplete, null);

    //делаем пока свою работу
    while (!isDone)
    {
          Thread.Sleep(100);
          Console.WriteLine("Делаем свою работу и ждем завершения потока");
     }
     res = b.EndInvoke(aRes);
     Console.WriteLine("Асинхронный вызов функции Add результат = {0}", res);

	//функция обратного вызова для делегата AsyncCallback
    private static void AddComplete(IAsyncResult aRes)
    {
        Console.WriteLine("Поток закончился функция AddComplete вызвана на потоке {0}", 
            Thread.CurrentThread.ManagedThreadId);
        isDone = true;
       
        //Хотелось бы, чтобы функция AddComplete бы информацие о делегате, который ее вызвал
        //надо добавить System.Runtime.Remoting.Messaging
        AsyncResult ar = (AsyncResult)aRes;
        BinaryOp b = (BinaryOp)ar.AsyncDelegate;
        //Воспользуемся четвертым параметром BeginInvoke, который является типом object
        Console.WriteLine((string)ar.AsyncState);
           
            
    }

Флаг isDone объявлен членом класса:

private static  bool isDone = false;

Хотелось бы, чтобы функция AddComplete получала бы информацию о делегате, который ее вызвал (AddComplete), для этого можно воспользоваться четвертым параметром:

aRes = b.BeginInvoke(x, y, AddComplete,
                     "Спасибо, что сложили два числа");

Работа с делегатом TreadStart

Теперь запустим два потока используя Thread и делегат ThreadStart (без параметра):

    Thread p1 = new Thread(CrazyPrinter.PrintNumbers);
    Thread p2 = new Thread(CrazyPrinter.PrintNumbers);
    p1.Start(); p2.Start();

    class CrazyPrinter
    {
        public static void PrintNumbers()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Process ID {0} print {1}", Thread.CurrentThread.ManagedThreadId, i);
                Thread.Sleep(1000);
            }

        }

        public static void PrintNumbersPar(object obj)
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Process ID {0} print {1}", Thread.CurrentThread.ManagedThreadId, i);

                Thread.Sleep(1000);
            }
            Console.WriteLine("Поток завершен - переданный параметр {0}", (string)obj);
           
        }

    }

Чтобы в поток передать параметр, можно воспользоваться объектом ParameterizedThreadStart:

   string s1 = "Вова хочет в Тамбов";
   string s2 = "Кто то хочет тоже";
   Thread p3 = new Thread(new ParameterizedThreadStart(CrazyPrinter.PrintNumbersPar));
   Thread p4 = new Thread(new ParameterizedThreadStart(CrazyPrinter.PrintNumbersPar));
   //в общем случае в качестве параметра выступает object. 
   //Дело компетенции потока привести к нужному типу
            p3.Start(s1); p4.Start(s2);

Пользоваться переменной isDone как флаг завершения потока небезопасно с точки зрения многопоточности. Для безопасности воспользуемся AutoResetEvent классом.

 //Используется для уведомления об окончании потока. false - означает, что уведомления нет пока 
    private static AutoResetEvent waitHandle = new AutoResetEvent(false);
Thread p5 = new Thread(new ParameterizedThreadStart(PrintNumbersPar));
p5.Start(s1);
//тормозимся и ждем уведомления
waitHandle.WaitOne();
Console.WriteLine("Поток завершился - событие уведомление от потока");
 //функция для потока p5 с уведомлением вызывающему потоку 
        public static void PrintNumbersPar(object obj)
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Process ID {0} print {1}", Thread.CurrentThread.ManagedThreadId, i);

                Thread.Sleep(1000);
            }
            Console.WriteLine("Поток завершен - переданный параметр {0}", (string)obj);
           
            //уведомление вызывающему потоку
            waitHandle.Set();
        }

Синхронизация потоков — ключевое слово lock.

Используем ключевое слово lock (нотация для Threading.Monitor) для общего ресурса множества потоков Эти потоки отработают последовательно, один за другим, не прерывая друг-друга благодаря тому, что процесс внутри lock не может быть прерван никаким другим процессом.

	Thread[] thread_arr = new Thread[10];
    CrazyPrinter_lock crazy = new CrazyPrinter_lock();
    for (int i = 0; i < 10; i++)
    {
        thread_arr[i] = new Thread(crazy.PrintNumbers);
    }
    for (int i = 0; i < 10; i++)
    {
        thread_arr[i].Start();
    }
           
	class CrazyPrinter_lock
    {
        //Маркер маркировки, для статческих функций он может быть статическим
        private object threadLock = new object();
        //разделяемая процессами переменноая
        public int j = 0;
        public void PrintNumbers()
        {
            lock (threadLock)
            {
                for (int i = 0; i < 3; i++)
                {
                    Random r = new Random();
                    Thread.Sleep(100 * r.Next(5));
                    Console.WriteLine("Process ID {0} print {1}", Thread.CurrentThread.ManagedThreadId, i);
                   
                }
            }
        }

Теперь все то же самое с Threading.Monitor, который обладает большими возможностями для применения.

	Thread[] thread_arr1 = new Thread[10];
    CrazyPrinter_lock crazy1 = new CrazyPrinter_lock();
    for (int i = 0; i < 10; i++)
    {
         thread_arr1[i] = new Thread(crazy1.PrintNumbers1);
    }
    for (int i = 0; i < 10; i++)
    {
         thread_arr1[i].Start();
    }
	public void PrintNumbers1()
    {
       Monitor.Enter(threadLock);
       try
       {

           for (int i = 0; i < 3; i++)
           {
                Random r = new Random();
                Thread.Sleep(100 * r.Next(5));
                Console.WriteLine("Process ID {0} print {1}", Thread.CurrentThread.ManagedThreadId, i);
            }
        }
        finally
        {
             Monitor.Exit(threadLock);
        }   
            
     }

Используем тип System.Threading.Interlocked для оперирования с одиночным типом данных CompareExchange(), Decrement(), Exchange(), Increment().

	Thread[] thread_arr2 = new Thread[10];
    CrazyPrinter_lock crazy2 = new CrazyPrinter_lock();
    for (int i = 0; i < 10; i++)
    {
           thread_arr2[i] = new Thread(crazy2.PrintNumbers2);
    }
    for (int i = 0; i < 10; i++)
    {
         thread_arr2[i].Start();
    }
 public void PrintNumbers2()
        {
            
            for (int i = 0; i < 3; i++)
              {
                 Random r = new Random();
                 Thread.Sleep(100 * r.Next(5));
                 Interlocked.Decrement(ref j);

                 Console.WriteLine("Process ID {0} print {1}", Thread.CurrentThread.ManagedThreadId, j);
              }
        }

И наконец — самый ленивый потокобезопасный способ для класса
используем атрибут [Synchronization]:

	using System.Runtime.Remoting.Contexts;...
    //Все методы Printer теперь безопасны к потокам!
    [Synchronization]
    public class Printer : ContextBoundObject
    {
      public void PrintNumbers()
                //{
                //}
    }

Timer — запуск потоков по расписанию.

Создадим делегат для вызова функции по таймеру.

	TimerCallback timerCallBack = new TimerCallback(callBack);
    Timer tim = new Timer(callBack, "Вася говорит!", 0, 1000);

	public static void callBack(object state)
    {
       Console.WriteLine("Московское время {0}, говорит {1}", DateTime.Now.ToLongTimeString(), state.ToString());
    }

Пул рабочих потоков CLR.

Среда исполнения обычно не создает отдельный поток, а использует пул рабочих потоков. В частности. BeginInvoke использует именно пул рабочих потоков. Можно запросить поток из пула непосредственным образом.
Потоки пула всегда являются фоновыми. Если вы хотите поток переднего плана, заводите себе отдельный поток и ставьте bgroundThread.IsBackground = false; — поток переднего плана
предохраняет приложение от завершения, пока он сам не закончиться.

 	CrazyPrinter cp = new CrazyPrinter();
    tim.Dispose();
    WaitCallback waitCalback = new WaitCallback(callBack1);
    for (int i = 0; i < 10; i++)
    {
          ThreadPool.QueueUserWorkItem(waitCalback, "обьект для передачи в поток");
    }
    Console.ReadLine();

 	public static void callBack1(object state)
        {
            Thread.Sleep(10000);
            Console.WriteLine("ID{0} нам передали:{1}", Thread.CurrentThread.ManagedThreadId, state.ToString());
        }

13 комментариев: C#: Работа с потоками.

Добавить комментарий

Ваш адрес email не будет опубликован.

Найти на сайте
Со страниц сайта
Метки
хохмаУмные мыслиармейский юморДела семейныеДокторинформацияой болитфольклорВовочка & kidsВадим ЗверевПолитическиеСтатусы ВКотактеСобрание скороговорокБольшие и малые народностиПро животныхЗаконы МерфиженщиныПро это...Забойный наборНиколай ФоменкоВсякая всякотаалкоманы-наркоголикиПро услуги и рестораныВиктор ШендеровичБородатые анекдотыавтомобилистыТуристы и турыИскусство и киноПро работуКозьма ПрутковПро студентовОмар ХайямЧерномырдинСтатусы про женщин и мужчинВ общественном транспортеПро сумасшедшихСтанислав Ежи ЛецКриминальныеПро ШтирлицаСтас ЯнковскийСмешные статусыДурацкие законыПро юристовпро самолетыПечалькаПро братковХорошие советыНе та ориентацияМарк ТвенСтатусы про жизньНа селеКрасноармейскиеГусары и поручикиДразнилкиИностранные анекдотыСказочныеХрюн МоржовФрансуа де ЛарошфукоЧерный юморЖан-Жак РуссоОхота и рыбалкаПрограммистыЛеди и джентельменыСпортНа бога надейся...МультяшкиБизнесСчиталкиУильям ШекспирГеоргий ФрумкерФрансис БэконПраздникиДикий западШутливая лотереяБедные и богатыеРаневскаяПьер Огюстен Карон де БомаршеСоветы и ответыДикариИсторические анекдотыНикколо МакиавеллиНаполеон БонапартЗагадкиsongswordpresstraditionalpluginпоговоркиC#старостьmysqlЧастушкиbackendjavascripthostingsshajaxphpстатистикапандемия
Больше Меньше
Архивы
Рейтинг@Mail.ru