Здравствуйте, жуковцы. Пришла пора для еще одной познавательной статьи, и так начнем.
Думаю каждый из вас сталкивался с необходимостью распаралелить и синхронизировать потоки и заодно получить результат их работы, некоторые справлялись с этой задачей, некоторые нет, а некоторые использовали костыли которые страшно представить. В этой статье я покажу способ который в некоторой степени упростит вам работу с потоками и поможет распаралелить выполнение ваших задач.
class Task<T, TResult>
{
internal delegate TResult MethodHandler(T arg);
internal delegate void CompleteHandler(object sender, TResult e);
private bool isComplite;
private bool inProgress;
private readonly AsyncCallback _callback;
public T Argument { get; set; }
public MethodHandler Handler { get; set; }
public event CompleteHandler Complete;
public bool IsComplete { get { return isComplite; } }
public bool InProgress { get { return inProgress; } }
public Task(){ }
public Task(MethodHandler func, T arg)
{
this._callback = callback;
this.Argument = arg;
this.Handler = func;
}
private void callback(IAsyncResult ar)
{
var sender = (Task<T, TResult>)ar.AsyncState;
TResult result = sender.Handler.EndInvoke(ar);
OnComplete(result);
}
public void Execute()
{
if (Handler == null)
throw new NullReferenceException("Handler property can't be null");
isComplite = false;
inProgress = true;
Handler.BeginInvoke(Argument, _callback, this);
}
private void OnComplete(TResult e)
{
isComplite = true;
inProgress = false;
CompleteHandler handler = Complete;
if (handler != null) handler(this, e);
}
}
Данный класс очень прост в использовании, достаточно создать экземпляр с генерик параметрами типов входных и выходных данных. Пример:
Код:
Task<int, string> task = new Task<int, string>(i => i.ToString("X2"), 57005);
int в треугольных скобках обозначает тип входящих данных, в примере это число 57005, а string это тип возвращаемый функцией переданной в первый параметр
Код:
i => i.ToString("X2")
.
Теперь можно запустить выполнение методом Execute():
Если вы запустили вышеприведенные строки то наверное уже заметили, что нихрена не происходит, это все потому что метод выполнился асинхронно и по окончанию вычислений спровоцировал событие, что-бы увидеть результат нужно подписаться на это событие. Пример:
потокобезопасность прикручивать или она гарантируется таском?
Таск не может быть потокобезопасным, так же как не может быть и не безопасным, это то же самое, что спросить - этот поток потокобезопасен? Класс Таск это обертка над потоком, по этому использование метод инвокера
Код:
MethodInvoker mi = () => listBox1.Items.Add(s);
if (listBox1.InvokeRequired)
listBox1.Invoke(mi);
else
listBox1.Items.Add(s);
обязательно из за небезопасности контролов и формы
Добавлено через 4 часа 36 минут
Всерьез задумался над тем, как все таки BackgroundWorker вызывает событие в STAThread не спровоцировал исключение, в общем покопался в сорсах и узнал пару интересных вещей. Результат в первом посте.
Если в кратце то асинхронное выполнение проходит через экземпляр класса AsyncOperation который в свою очередь перенаправляет выполнение в главный поток
________________
Talk is cheap. Show me the code
— Linus Torvalds
Последний раз редактировалось Yukikaze; 16.11.2012 в 21:30.
Причина: Добавлено сообщение
Я понимаю что это создавалось исключительно в самообразовательных целях, но все же стоит почитать:
[Ссылки могут видеть только зарегистрированные пользователи. ].
[Ссылки могут видеть только зарегистрированные пользователи. ].
PS: О, напишу цикл статей про паттерны и где их надо использовать...
Последний раз редактировалось Sinyss; 18.11.2012 в 02:10.
Куда же еще больше упрощать, это же и так максимально упрощенный генерик бекграундворкер с некоторыми модификациями.
ЗЫ Как по мне использование шаблонов это хорошая практика для предотвращения постоянного анбоксинга при касте <тип> <-> object <-> <тип>
Куда же еще больше упрощать, это же и так максимально упрощенный генерик бекграундворкер с некоторыми модификациями.
А ведь можно было наследовать от него и дописать нужные реализации событий, ну и добавить в нужном моменте вызов события ReportProgress... по сути он будет всегда в одном и том же месте...
Sinyss, ну это невозможно как минимум потому, что нельзя перегрузить события, а это убивает всю задумку
Почему нельзя перезагрузить события? Там все нужные функции virtual, да и конкретную реализацию нужного события мы всегда можем переписать на нужный нам метод...
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler( backgroundWorker1_ProgressChanged);
// какие то действия
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler( backgroundWorker1_New_ProgressChanged);
Вот мы и перегрузили событие...
ИМХО событие это просто ссылка на функцию, которую мы можем в любой момент заменить на новую реализацию не удаляя старую
Последний раз редактировалось Sinyss; 18.11.2012 в 03:18.
Sinyss, вообще-то в приведенном примере ты подписался на событие, а виртуалом отмечены только евент инвокаторы которые практически всегда одинаковые, с заглушкой от NullReferenceExeption'а
а делегат SomeDeleagte мне по какой то причине не подходит, например потому, что принимает в параметр только EventArgs, а мне нужен int.
В общем возможно добавить только новые события и вызывать их старыми инвокаторами.
Короче с таким же успехом можно сказать : "Используй ThreadPool вместо BackgroundWorker'а зачем усложнять" ведь BackgroundWorker это обертка над Delegate.BeginInvoke который в свою очередь передает выполнение функции в ThreadPool. ASyncTask и BackgroundWorker используют разные реализации одних и тех же действий.
Я лично использую лямбды в таких случаях.
ps: Экспериментировал со всеми вариациями распараллеливания и работает лучше всех Parallel, результаты тестов привести не смогу, ибо делались специфические с огромными математическими задачами(от 300кк итераций), но, возможно для небольших задач подойдут Thread или Task