Информация об изменениях

Сообщение Re[6]: Объясните поведение ContinueWith(..., TaskContinuatio от 02.01.2017 21:56

Изменено 02.01.2017 21:58 AK107

Re[6]: Объясните поведение ContinueWith(..., TaskContinuationOptions.ExecuteSync
Здравствуйте, Sinix, Вы писали:

S>Вообще, закладываться на такое поведение опасно, т.к. это просто побочный эффект реализации и никто не запрещает .WhenAll добавляться в начало списка продолжений. Лично я бы переписал код на await-ах или сохранял бы в список запущенные задачи. Все прочие варианты (включая AttachedToParent) ненадёжны.


оценку я не заслужил

вопрос о порядке выполнения возник давеча когда делал custom target для NLog. там такой изврат на самописных continuations для асинхронного логирования да еще и не задокументированный нормально. особый головняк который нужно реализовывать — это FlushAsync, где, как оказалось, нужно дождаться реального завершения всех активных тасков. можно сделать и на await, но придется городить обработку ошибок, тогда как здесь ничего лишнего. вот такое получилось:
        private readonly ConcurrentDictionary<AsyncLogEventInfo, Task> pendingEvents = new ConcurrentDictionary<AsyncLogEventInfo, Task>();

        protected sealed override void Write(AsyncLogEventInfo info)
        {
            var task = WriteAsync(info);

            pendingEvents.TryAdd(info, task);

            task.ContinueWith(x =>
            {
                Task tmp;
                pendingEvents.TryRemove(info, out tmp);
            }, 
            TaskContinuationOptions.ExecuteSynchronously);
        }

        private async Task WriteAsync(AsyncLogEventInfo info)
        {
            try
            {
                var result = await Client.PostAsync(...);

                result.EnsureSuccessStatusCode();

                info.Continuation(null);
            }
            catch (Exception ex)
            {
                info.Continuation(ex);
            }
        }

        protected override void FlushAsync(AsyncContinuation asyncContinuation)
        {
            Task.WhenAll(pendingEvents.Values)
                .ContinueWith(x => asyncContinuation(x.Exception), TaskContinuationOptions.ExecuteSynchronously);
        }

в принципе требования удаления из pendingEvents до фактического завершения FlushAsync (через ContinueWith) не критично, но хотелось гарантий на будущее расширение функционала. добавлять же сам ContinueWith в pendingEvents некошерно, т.к. в этом случае, теоретически, TryRemove может отработать до TryAdd.

вопрос: разве ExecuteSynchronously в данном решении не дает гарантий, что первый COntinueWith выполнится до второго?

S>P.S. исходники удобнее смотреть на https://referencesource.microsoft.com/ или https://source.dot.net/ .

спасибо!
Re[6]: Объясните поведение ContinueWith(..., TaskContinuatio
Здравствуйте, Sinix, Вы писали:

S>Вообще, закладываться на такое поведение опасно, т.к. это просто побочный эффект реализации и никто не запрещает .WhenAll добавляться в начало списка продолжений. Лично я бы переписал код на await-ах или сохранял бы в список запущенные задачи. Все прочие варианты (включая AttachedToParent) ненадёжны.


оценку я не заслужил

вопрос о порядке выполнения возник давеча когда делал custom target для NLog. там такой изврат на самописных continuations для асинхронного логирования да еще и не задокументированный нормально. особый головняк который нужно реализовывать — это FlushAsync, где, как оказалось, нужно дождаться реального завершения всех активных тасков. можно сделать и на await, но придется городить обработку ошибок, тогда как здесь ничего лишнего. вот такое получилось:
        private readonly ConcurrentDictionary<AsyncLogEventInfo, Task> pendingEvents = new ConcurrentDictionary<AsyncLogEventInfo, Task>();

        protected sealed override void Write(AsyncLogEventInfo info)
        {
            var task = WriteAsync(info);

            pendingEvents.TryAdd(info, task);

            task.ContinueWith(x =>
            {
                Task tmp;
                pendingEvents.TryRemove(info, out tmp);
            }, 
            TaskContinuationOptions.ExecuteSynchronously);
        }

        private async Task WriteAsync(AsyncLogEventInfo info)
        {
            try
            {
                var result = await Client.PostAsync(...);

                result.EnsureSuccessStatusCode();

                info.Continuation(null);
            }
            catch (Exception ex)
            {
                info.Continuation(ex);
            }
        }

        protected override void FlushAsync(AsyncContinuation asyncContinuation)
        {
            Task.WhenAll(pendingEvents.Values)
                .ContinueWith(x => asyncContinuation(x.Exception), TaskContinuationOptions.ExecuteSynchronously);
        }

в принципе требования удаления из pendingEvents до фактического завершения FlushAsync (через ContinueWith) не критично, но хотелось гарантий на будущее расширение функционала. добавлять же сам ContinueWith в pendingEvents или переносить в тело WriteAsync некошерно, т.к. в этом случае, теоретически, TryRemove может отработать до TryAdd.

вопрос: разве ExecuteSynchronously в данном решении не дает гарантий, что первый COntinueWith выполнится до второго?

S>P.S. исходники удобнее смотреть на https://referencesource.microsoft.com/ или https://source.dot.net/ .

спасибо!