понедельник, 23 августа 2010 г.

Типичные вопросы на понимание работы делегатов, событий и многопоточности.

 
Я часто бывал на собеседованиях, но некоторые вопросы ставили меня в тупик, кстати, и запоминались они почему-то после собеседований. Поэтому приходя домой, я обязательно искал ответы на них. Вот некоторые вопросы, которые мне приходилось слышать на собеседовании по темам “Делегаты” и “Потоки”. Я приведу свои ответы. Возможно они не во всём правильные, было бы интересно подискуссировать с вами на эти темы. Жду ваших комментариев J
Делегаты и события.
  1. Переделайте пример так, чтобы он работал только с параметрами типа int(Я переделывал наоборот из int во float, ниже мой ответ и одновременно задание для вас).
class Program
{
public delegate float TypeSafeAddFunctionDelegate(float x, float y);
public static float Add(float x, float y)
{
Console.WriteLine(x+y);
return x + y;
}
static void Main()
{
float z;
TypeSafeAddFunctionDelegate f = new TypeSafeAddFunctionDelegate(Add);
z = Add(13.2f, 17.2f);
z = f(13.2f, 17.2f);
}
}
  1. Создайте консольное приложение, которое вызывает функцию EnumWindows из Win32 API и перебирает все окна верхнего уровня.
public delegate bool CallBackDelegate(int hwnd, int lParam);
public class Program
{
[DllImport("user32")]
public static extern int EnumWindows(CallBackDelegate x, int y);
public static void Main()
{
CallBackDelegate myCallBack = new CallBackDelegate(Report);
EnumWindows(myCallBack, 0);
}
public static bool Report(int hwnd, int lParam)
{
Console.Write("Window handle is ");
Console.WriteLine(hwnd);
return true;
}
}
  1. Придумайте хотя бы два примера использования делегатов.
    • Делегаты используются по двум основным причинам:
1) Делегаты обеспечивают поддержку функционирования событий.
2) Делегаты позволяют во время выполнения программы выполнить метод, который точно не известен во время компиляции.
§ Делегаты наделены возможностью группового вызова. Также делегаты могут приниматься в виде параметров в функции. Следующий пример демонстрирует это.
class Dog
{
//Имя собаки
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
//Грязная ли собака?
private bool isDirty;
public bool IsDirty
{
get { return isDirty; }
set { isDirty = value; }
}
//Привитая ли собака?
private bool isInoculated;
public bool IsInoculated
{
get { return isInoculated; }
set { isInoculated = value; }
}
//Конструктор
public Dog(string name, bool isDirty, bool isInoculated)
{
this.name = name;
this.isDirty = isDirty;
this.isInoculated = isInoculated;
}
//Делегат.Может вызывать любой метод, получающий объект Dog
//в виде параметра и не возвращающий ничего
public delegate void DogDelegate(Dog d);
}
//Собачий питомник
class Kennel
{
//Список собак в питомнике
ArrayList theDog = new ArrayList();
//Конструктор
public Kennel()
{
theDog.Add(new Dog("Tuzik",true,false));
theDog.Add(new Dog("Lessy",true,true));
theDog.Add(new Dog("Rex",false,true));
}
//Ветеринарный осмотр
public void VeterinaryExamination(Dog.DogDelegate exam)
{
foreach (Dog d in theDog)
{
Console.WriteLine("Осмотр собаки "+d.Name);
exam(d);
}
}
}
//Ветеринарный пункт
class VeterinaryStation
{
//Моем собаку
public void WashDog(Dog d)
{
if (d.IsDirty)
Console.WriteLine("Моем собаку");
else
Console.WriteLine("Собака уже помыта");
}
//Прививаем собаку
public void InoculateDog(Dog d)
{
if (d.IsInoculated)
Console.WriteLine("Делаем прививку");
else
Console.WriteLine("Прививка уже сделана");
}
}
class Program
{
static void Main(string[] args)
{
Kennel kennel = new Kennel();
VeterinaryStation station = new VeterinaryStation();
//Осмотр собак
kennel.VeterinaryExamination(new
Dog.DogDelegate(station.WashDog));
kennel.VeterinaryExamination(new
Dog.DogDelegate(station.InoculateDog));
Console.ReadLine();
}
}
§ При обращении к делегату, содержащему в списке вызова несколько функций, вовсе не гарантируется, что все функции из списка будут вызваны. Если одна из них сгенерирует исключение, остальные функции вызваны не будут. Обойти проблему можно следующим образом.
class Program
{
delegate void BynaryOp(string x, string y);
static void Add(string x, string y)
{
int sum =Int32.Parse(x) + Int32.Parse(y);
Console.WriteLine("Сумма = " + sum);
}
static void Subtract(string x, string y)
{
int sub = Int32.Parse(x) - Int32.Parse(y);
Console.WriteLine("Разность = " + sub);
}
static void Main()
{
//Если ввести некорректные x и y, то будет сгенерировано
//исключение в методах на которые указывает делегат
string x=Console.ReadLine();
string y = Console.ReadLine();
BynaryOp del = new BynaryOp(Add);
del += new BynaryOp(Subtract);
foreach (BynaryOp d in del.GetInvocationList())
{
// Обернем вызов функции в защищенный
// блок, что позволит предотвратить
// преждевременное завершение алгоритма.
try
{
d(x, y);
}
catch (Exception ex)
{
Console.WriteLine("Some exception has occurred");
}
}
}
}
  1. Подумайте, какие из следующих утверждений, касающихся событий в C#, верны, а какие – нет.
A. Много подписчиков могут подписаться на одно событие – верно, добавление подписчиков происходит с помощью операции “+=”.
B. События не могут нести контекстной информации вместе с собой. – не верно.
C. События нотифицируют подписчиков в том случае, если что-то произошло.- верно, нотифицируют , если произошло это событие.
D. Публикатор события определяет, когда событие будет вызываться.- верно.
E. События базируются на делегатах.- верно. Обработчики соытий представляются делегатами..
  1. Обсудите преимущества использования событий.
    • При обработке ключевого слова event компилятор автоматически создаёт для вас методы регистрации, а также члены переменные, необходимые для типа делегата.
    • Использование событий упрощает регистрацию обработчиков событий вызывающей стороны.
Потоки.
  1. Каким из следующих способов можно запустить поток newProcess?
A. newProcess.Start(); - верно.
B. newProcessEntry.Start(); - не верно. Нет такого метода.
C. newProcess.Start(newProcessEntry); - не верно. Не верный параметр.
D. newProcessEntry.Run(); - не верно. Нет такого метода.
E. newProcess.Run(); - не верно. Нет такого метода.
  1. Напишите программу, создающую три потока одного типа. Каждому из потоков в параметре конструктора должна передаваться строка с его именем (например, «Первый»), затем он должен прокручиваться в цикле n раз (n задается в параметрах командной строки) и завершать свою работу. При каждом проходе цикла поток должен печатать номер прохода цикла и свое имя, затем засыпать на какой-то промежуток времени и продолжать выполнение. При завершении поток должен печатать строку следующего формата: «Поток <Имя> завершен».
class Program
{
static private int n;
static void Main(string[] args)
{
if (args.Length == 0)
{
return;
}
else
{
n=Int32.Parse(args[0]);
}
Thread thread1 = new Thread(func);
Thread thread2 = new Thread(func);
Thread thread3 = new Thread(func);
thread1.Start("Thread1");
thread2.Start("Thread2");
thread3.Start("Thread3");
}
static void func(object name)
{
Random rand = new Random();
Thread.CurrentThread.Name=(string)name;
for (int i = 0; i < n; i++)
{
Console.WriteLine("Номер цикла " + n +","+name);
Thread.Sleep(rand.Next(1000));
}
Console.WriteLine("Поток "+ name+"завершён.");
}
}
  1. Что такое «критическая секция» в терминах многопоточности?
    • Критические секции - это объекты, используемые для блокировки доступа всех нитей (threads) приложения, кроме одной, к некоторым важным данным в один момент времени.
  2. Приведите хотя бы два примера задач, в которых фигурирует критическая секция.
    • Чтение/запись в файл
    • Использование принтера
  3. Каким образом можно выделить критическую секцию в C#?
 
      Monitor.Enter(this);
      m_dwSmth = dwSmth;
      Monitor.Exit(this);
  1. Какие из следующих способов использования lock являются правильными?
A. Неверно
public void InsertData()
{
try
{
lock{_cmdBCP.ExecuteNonQuery();}
}
catch
{
System.Console.WriteLine("Caught an exception");
}
finally {
unlock;
}
}
B. Неверно
public void InsertData()
{
try
{
lock(this) {_cmdBCP.ExecuteNonQuery();}
}
catch
{
System.Console.WriteLine("Caught an exception");
}
finally
{
unlock(this);
}
}
C. Неверно
public void InsertData()
{
try
{
lock(this) {_cmdBCP.ExecuteNonQuery();}
}
catch
{
System.Console.WriteLine("Caught an exception");
}
finally
{
lock.Exit(this);
}
}
D.Верно
public void InsertData()
{
try
{
lock(this) {_cmdBCP.ExecuteNonQuery();}
}
catch
{
System.Console.WriteLine("Caught an exception");
}
}
E. Неверно
public void InsertData()
{
try
{
lock(_cmdBCP.ExecuteNonQuery())
}
catch
{
System.Console.WriteLine("Caught an exception");
}
finally
{
unlock();
}
}
  1. Обсудите преимущества и недостатки использования многопоточности.

    • Недостатки:
· Значительное увеличение сложности программ.
· Чрезмерное использование многопоточности отнимает ресурсы и время CPU на создание потоков и переключение между потоками.
· Увеличивается вероятность возникновения трудноуловимых ошибок в программе.
§ Достоинства:
· Методы выполняться быстрее, если рабочая нагрузка разнесена по нескольким потокам.
· Возможность выполнять длительные вычисления в фоновом режиме.
· При выполнении вычислений в фоновом режиме, приложение не выглядит зависшим и способно реагировать на запросы клавиатуры, мыши или на др. действия.
Обязательные правила любого программиста
И напоследок несколько обязательных правил, которые я считаю нужно везде и всегда исполнять, даже, особенно на собеседовании, когда вас просят сделать тестовое задание или написать кусок кода. В первую очередь они смотрят на стиль вашего кодирования, на аккуратность и понятность кода. Поэтому думая над решением задачи не забывайте об основных правилах:
· Код нужно комментировать, используя XML-комментарии.
http://msdn2.microsoft.com/en-us/library/b2s063f7(vs.71).aspx
· Каждый класс должен быть в отдельном файле, название которого должно совпадать с названием класса. То же самое касается интерфейсов и структур.
Другое именование приведет к серьезной путанице на большом проекте в команде.

· Прежде чем сдавать задания на проверку, убедитесь, что проект компилируется и запускается, что все пункты ТЗ выполнены.

· Приложение не должно выбрасывать необработанные исключения.
По поводу этого правила обязательно почитайте Рихтера, глава 18.
Если вы отображаете сообщение об исключении пользователю, то оно должно быть понятным ему, а также содержать указания (явно или неявно), что нужно сделать, чтобы исключение не возникало.
  • Память должна освобождаться корректно для неуправляемых ресурсов.
По поводу этого правила обязательно почитайте Рихтера, глава 19.
  • Необходимо разделять логику и интерфейс.
MessageBox – это часть пользовательского интерфейса. Следовательно, использовать его в логике не следует.

  • В каждом приложении должна быть реализована защита от дурака (если ожидаем, что пользователь введет целое число, а пользователь ввел строку, приложение должно не упасть, а корректно обработать данную ситуацию).
  • Если функция получается громоздкой (больше экрана), необходимо разделить ее на несколько функций по смыслу.

  • Все решения нужно писать в соответствии с ООП.
Операции, производимые над объектом, должны быть описаны в функциях в классе этого объекта. Все общее между классами выносится в базовые классы.

  • Необходимо избегать дублирования в коде, за Copy/Past бывает расстрел на месте J
Если есть что-то общее в методах, значит нужно выносить это общее в новые методы и вызывать их, либо оптимизировать методы так, чтобы вместо двух похожих был один
Иерархию классов нужно прорабатывать так, чтобы между классами не было никакого дублирования, то есть всё общее нужно выносить в базовые абстрактные классы
Вот, пожалуй, самое основное, что я всегда помню и соблюдаю. Хочу поблагодарить школу программирования компании “Epam Systems”, которая в своё время заставила меня это заучить на всю жизнь.

2 комментария:

  1. > Критические секции - это объекты, используемые для блокировки доступа всех нитей (threads) приложения, кроме одной, к некоторым важным данным в один момент времени.

    ммм... это скорее определение мьютекса.

    Критическая секция - это участок кода во время исполнения которого доступ к одному объекту может произойти из нескольких потоков исполнения.

    Соотв. из-за этого могут происходить ошибки(например два потока пытаются одновременно читать из файла или писать в него - нехорошо получится).

    ОтветитьУдалить
  2. п.с. могу даже дать ссылку на литературу:
    Д.В. Иртегов "Введение в операционные системы" стр. 378

    даётся опр. КС - это интервал, в течение которого модификация нарушает целостность разделяемой структуры данных, и, наоборот, интервал, в течение которого алгоритм нити полагается на целостносить этой структуры.

    а достигается целостность структуры данных уже за счёт различных объектов реализованных в коде(мьютексы, семафоры, мониторы и т.д.)

    ОтветитьУдалить