пятница, 13 июля 2012 г.

Пауза и возобновление работы потока

Потоки в программировании весьма полезная вещь. Многопоточность применяется там, где необходимо разгрузить основной поток от "тяжёлых" задач. Многопоточность в C# имеет свою специфику. В этой статье будет уделено внимание задаче приостановки работы потока и его возобновлению с места остановки.

За работу с потоками в C# отвечают несколько классов: во-первых это класс Thread (System.Threading), представляющий отдельный поток. А во-вторых - это ThreadPool, дающий возможность работы с пулом потоков. Пул потоков предпочтительнее для использования, да и удобнее он в реализации. Пул потоков - это очередь потоков приложения, в эту очередь вы можете вставить свой метод, который будет выполнен в отдельном потоке, когда один из потоков освободится.
Многие современные приложения используют возможность паузы выполнения какой то задачи. В тех же антивирусных сканерах обязательно имеются кнопки паузы для вирусного сканирования и его возобновления. Такие задачи как поиск вирусов в антивирусных комплексах реализованы почти наверняка через многопоточность, так как же приостановить выполнение потока, а потом его опять запустить с того же места? Попробуем в этом разобраться...

Создадим простейшее приложение на C#, которое будет использовать отдельный поток для вывода информации. На форме у нас должны располагаться три кнопки, в свойстве Text которых можно написать: Старт, Пауза, Стоп. Так же нам понадобится метка label.
Разместив необходимые компоненты на форме, можно приступать к кодингу. Объявим несколько глобальных переменных и напишем кое-что в конструктор формы:

EventWaitHandle ew;
        bool Stop, Pause;
        public Form1()
        {

            InitializeComponent();
            ew = new EventWaitHandle(false, EventResetMode.AutoReset);
            Stop = true;
        }
Здесь мы объявили объект класса EventWaitHandle, именно с помощью этого класса и происходит сигнализация потоку о необходимости остановке или возобновления своей работы. Так же мы объявили две булевых переменных.
На кнопку Старт повесим код:

private void button1_Click(object sender, EventArgs e)
        {
            if (Stop)
            {
                button2.Enabled = button3.Enabled = true;
                button1.Enabled = false;
                Stop = false;
                ThreadPool.QueueUserWorkItem(new WaitCallback((s) =>
                {
                    ThreadMethod(100);
                }));
            }
            else
            {
                ew.Set();
                button3.Enabled = true;
            }
        }
Так же создадим метод, который будет выполняться в отдельном потоке:

void ThreadMethod(int value)
        {
            Action<int> act = new Action<int>((v) =>
                {
                    label1.Text = v.ToString();
                });

            int i = 0;
            while (!Stop && i <= value)
            {
                if (Pause)
                {
                    ew.WaitOne();
                    Pause = false;
                }
                Thread.Sleep(500);
                i += 1;
                if (label1.InvokeRequired)
                    label1.Invoke(new MethodInvoker(() =>
                        {
                            act(i);
                        }));
                else
                    act(i);
            }
        }
Настала пора немного рассказать про этот загадочный EventWaitHandle. Экземпляр данного класса имеет много методов, наиболее полезные из которых это - WaitOne() и Set(). WaitOne() служит для приостановки действия потока, а Set - для возобновления его работы с места остановки. По нажатию кнопки Старт у нас в пул потоков помещается метод, который будет выполнен в отдельном потоке. Если вы не знакомы с лямбда-выражениями, то советую вам побыстрее исправлять это, т.к лямбды - чрезвычайно упрощают написание кода. В метод ThreadMethod, который мы запускаем в новом потоке мы передаём в параметре число 100.

Особенность работы с WinForm в C# заключается в том что доступ к компоненту можно получить только из того потока. в котором данный компонент создан, соответственно, если попытаться достучаться из другого потока до свойств компонента не созданного в этом потоке,  то велика вероятность возникновения ошибок. Для решения этой проблемы у всех компонентов имеется метод Invoke в который необходимо передать делегат (ссылку на метод). Через свойство InvokeRequired проверяется необходимость доступа через Invoke, если InvokeRequired равен false значит метод можно запустить и без Invoke.
На кнопки Пауза и Стоп повесим код, соответственно:

private void button2_Click(object sender, EventArgs e)//PAUSE
        {
            Pause = true;
            button3.Enabled = false;
            button1.Enabled = true;
        }

        private void button3_Click(object sender, EventArgs e)//STOP
        {
            Stop = true;
            button2.Enabled = false;
            button1.Enabled = true;
        }

Что же делает наше приложение? По нажатию кнопки Старт у нас запускается поток в котором меняется свойство Text label'a с задержкой в 500mc. При нажатии на Паузу, булева переменная Pause становится равна истине и в ThreadMethhod'e поток приостанавливается при помощи WaitOne(). При нажатии Старт поток возобновляет работу с места остановки. При нажатии Стоп поток останавливается, уничтожается. Вот такое незамысловатое приложение

Исходники: http://dl.dropbox.com/u/16627362/threadPause.rar

Автор: Богданов М. 
13.07.2012



четверг, 12 июля 2012 г.

C#: Linq to Access


Linq - чудесная технология, суть которой заключается в манипулировании данными посредством Sql-подобного языка запросов. Для работы с базами данных используется так называемый Linq2Sql - это всё тот же Linq, но ориентированный на базы данных.

В этой статье мы попытаемся применить LINQ для работы с базой данных на Access. Почему Access? Просто LINQ по умолчанию поддерживает работу только с MS Server, чтобы заставить его работать с другими субд, надо немного попотеть.
Что ж, приступим...
Для начала я создал простецкую бд с двумя таблицами: Сотрудник и Должность. Соответственно, каждый сотрудник имеет свою фамилию, имя, отчество и должность, которая оценивается в денежном эквиваленте. Такая база данных создана только в качестве примера, дабы было на чём тренироваться при работе с Linq.


Как я уже говорил, Linq не предназначен для работы с Access. Чтобы заставить его работать с этой субд можно воспользоваться библиотекой Alinq, которую можно скачать по ссылке: http://www.alinq.org/download/ALinq_V2.6.2.msi
После установки, все необходимые файлы у меня разместились в C://ProgramFiles(x86)/alinq
Мы ещё будем обращаться к этому каталогу в ходе реализации приложения.
Итак, начнём создавать наше приложение. Надеюсь, вы сами сможете создать новый проект.
Далее нам необходимо подключить необходимые библиотеки для работы с Linq To Access. В Solution Explorer'e вызовем контекстное меню и выберем Add Reference. Далее на вкладке Browse  перейдём в каталог, куда установился Alinq и добавим 2 библиотеки: Alinq.Access.dll и Alinq.dll
В главной форме добавим ссылки на сборки:
using ALinq.Access;
using ALinq;

Далее нам нужно создать классы, которые соответствуют таблице в бд. Класс Worker имеет следующий вид:
using System;
using System.Collections.Generic;
using System.Text;
using ALinq.Mapping;

namespace LinqAccess1
{
    [Table(Name="Сотрудник")]
    public class Worker
    {
        [Column(Name="Код",IsDbGenerated=true, IsPrimaryKey=true)]
        public int ID{get;set;}

        [Column(Name="Фамилия")]
        public string FirstName{get;set;}

        [Column(Name="Имя")]
        public string Name{get;set;}

        [Column(Name="Отчество")]
        public string SecondName{get;set;}

        [Column(Name="Должность")]
        public int WorkType{get;set;}

    }
}

Немного поясню представленный код. В квадратных скобках указываются атрибуты свойств. Именно таким образом Linq может понять что класс Worker - это сущность, которая соответствует таблице Сотрудник. У таблицы есть поля, которые представлены полями в классе. Опять же, данные поля описаны соответствующими атрибутами, так например поле 
Код таблицы бд, представлено в классе полем:

[Column(Name="Код",IsDbGenerated=true, IsPrimaryKey=true)]

public int ID{get;set;};

Где в кв. скобках сказано (если переводить на вольный русский язык), что это поле соответствует полю "Код" в бд, это поле является первичным ключом.
Так как у нас в бд две таблицы, то необходимо создать ещё один класс - WorkTypes, который будет отвечать за таблицу Должность.
using System;
using System.Collections.Generic;
using System.Text;
using ALinq.Mapping;
namespace LinqAccess1
{
    [Table(Name="Должность")]
    public class WorkTypes
    {
        [Column(Name = "Код", IsPrimaryKey = true, IsDbGenerated = true)]
        public int ID { get; set; }

        [Column(Name = "Должность")]
        public string WorkTypeName { get; set; }

        [Column(Name = "Зарплата")]
        public int Zarplata { get; set; }
    }
}



После реализации соответствующих сущностей, попробуем отобразить содержимое этих таблиц. Кинем на форму dataGridView. Объявим глобальную переменную типа DataContext. Этот тип по отвечает за соединение с бд и за представление данных, находящихся в таблицах.

public partial class Form1 : Form
    {
        Table<Worker> workers;
        DataContext context;
        public Form1()
        {
            InitializeComponent();
            context = new DataContext(@"db1.mdb");


            workers = context.GetTable<Worker>();
            dataGridView1.DataSource = workers;                
        }
Здесь в конструкторе формы мы инициализируем переменную context, где в параметрах конструктора передаём путь до нашей базы данных. Далее мы получаем содержимое таблицы Сотрудник, и отображаем данные в гриде.
В гриде у нас отображается информация из таблицы Сотрудник, при этом мы не написали ни единого Sql-запроса. В столбце WorkType у нас находятся внешние ключи, эти двоечки, единички совершенно не радуют глаз, т.к не дают пользователю совершенно никакой информации о том, на какой должности работает сотрудник (откуда он может знать, что цифра 4 соответствует программисту..). Дабы решить эту проблему составим linq-запрос и повесим его на кнопку:
        private void button1_Click(object sender, EventArgs e)
        {
            IQueryable query=from person in workers join worktype in context.GetTable<WorkTypes>()
                              on person.WorkType equals worktype.ID select new {FirstN=person.FirstName,Name=person.Name,Second=
                                  person.SecondName,Worktype=worktype.WorkTypeName};

            dataGridView1.DataSource = query;
        }
Теперь в столбце WorkType отображается соответствующая должность, которую занимает сотрудник.
Пойдём ещё дальше, и составим запрос на выборку сотрудников, у которых з/п  >= значению в textBox'e
IQueryable q = from worker in workers
                           join worktype in context.GetTable<WorkTypes>() on
                           worker.WorkType equals worktype.ID
                           where worktype.Zarplata >= int.Parse(textBox1.Text)
                           select new
                           {
                               FirstName = worker.FirstName,
                               Name = worker.Name,
                               Worktype = worktype.WorkTypeName,
                               zarpl = worktype.Zarplata
                           };
            dataGridView1.DataSource = q;

В целом, тестовый пример готов. Наверняка у вас возникли сложности в понимании linq запросов, для этого рекомендую вам книгу Д.Албахари Карманный справочник по Linq в C#. Данная книга позволит вам в краткие сроки постичь Linq

Исходники: приложение не будет работать при не установленном Alinq!
http://dl.dropbox.com/u/16627362/LinqAccess1.rar

Автор:
Богданов М. 
12.07.2012