В этот раз я достаточно внимательно прочитал перевод главы про задачи(Tasks) из этого Поста, чтобы выяснить, что он не очень точно передает смысл исходного текста. Я попробую пересказать содержание так, как я его понял, в том числе полагаясь на свой практический опыт программирования многопоточных приложений и embedded приложений с множеством прерываний.
Мне кажется, я придумал хороший формат, чтобы совместить свой пересказ содержания достаточно близко к смыслу исходного текста (надеюсь), с моими комментариями-разъяснениями-догадками.
Пару ссылок на мои предыдущие работы по этой теме вы тоже найдете под катом.
Вот пара ссылок на предыдущие материалы по теме:
1. Уроки по асинхронному программированию из первой половины работы
2. Async/Await из C#. Головоломка для разработчиков компилятора и для нас
То что относится к пересказу содержания выделено через .NET блога я уже несколько раз обращал внимание что содержание этого Поста крутится вокруг термина-понятия «продолжение» это callback-функция которая должна быть вызвана при завершении асинхронной операции
И я бы не сказал что именно это является ключевой особенностью Task. По моему ключевой особенностью Task является то, что Task представляет всю-целую операцию, а не только ее завершение, но автор должен поддерживать линию повествования, которую он выбрал, поэтому я рассматриваю такие отклонения не как ошибку, а как способ сосредоточить внимание читателя на выделенном аспекте решения, которому посвящена работа, то есть на проблеме с назначением продолжения для операции в нужном контексте и как на этих продолжениях строится логика компиляции.>
class MyTask { private bool _completed; private Exception? _error; private Action<MyTask>? _continuation; private ExecutionContext? _ec; ... }
class MyTask { ... public void ContinueWith(Action<MyTask> action) { lock (this) { if (_completed) { ThreadPool.QueueUserWorkItem(_ => action(this)); } else if (_continuation is not null) { throw new InvalidOperationException("Unlike Task, this implementation only supports a single continuation."); } else { _continuation = action; _ec = ExecutionContext.Capture(); } } } }
<Это та самая иллюстрация в коде обозначенная (А). Помните что это реализация для метода из класса MyTask, который мы пишем и разбираем в целях обучения. В следующем абзаце с выделением код этой функции, по сути, просто переписан в словах. Единственное что там пропущено это пояснения для использования Этот lock нужен как раз чтобы разрешить ситуацию когда class MyTask { ... public void SetResult() => Complete(null); public void SetException(Exception error) => Complete(error); private void Complete(Exception? error) { lock (this) { if (_completed) { throw new InvalidOperationException("Already completed"); } _error = error; _completed = true; if (_continuation is not null) { ThreadPool.QueueUserWorkItem(_ => { if (_ec is not null) { ExecutionContext.Run(_ec, _ => _continuation(this), null); } else { _continuation(this); } }); } } } }
class MyTask { ... public void Wait() { ManualResetEventSlim? mres = null; lock (this) { if (!_completed) { mres = new ManualResetEventSlim(); ContinueWith(_ => mres.Set()); } } mres?.Wait(); if (_error is not null) { ExceptionDispatchInfo.Throw(_error); } } ...
public static MyTask WhenAll(MyTask t1, MyTask t2) { var t = new MyTask(); int remaining = 2; Exception? e = null; Action<MyTask> continuation = completed => { e ??= completed._error; // just store a single exception for simplicity if (Interlocked.Decrement(ref remaining) == 0) { if (e is not null) t.SetException(e); else t.SetResult(); } }; t1.ContinueWith(continuation); t2.ContinueWith(continuation); return t; }
public static MyTask Run(Action action) { var t = new MyTask(); ThreadPool.QueueUserWorkItem(_ => { try { action(); t.SetResult(); } catch (Exception e) { t.SetException(e); } }); return t; }
public static MyTask Delay(TimeSpan delay) { var t = new MyTask(); var timer = new Timer(_ => t.SetResult()); timer.Change(delay, Timeout.InfiniteTimeSpan); return t; }
https://habr.com/ru/articles/785828/lock (this)callback-продолжение будет передано в Задачу, когда Задача завершается одновременно с запросом о таком уведомлении. То есть когда вызов функции, которая передает продолжение в Задачу накладывается на обработку завершения операции из Задачи. Таким образом исключается race conditions в этом случае.>
Добавить комментарий