Ответ 1
Перередактировать. Я обменялся несколькими электронными письмами с помощью Stephen Toub. Ниже я пытаюсь объединить свой первоначальный ответ и его ответы в единое целое.
tl; dr, чтобы обойти это, принудительно CompleteSynchronously
всегда возвращать false (оставьте не-лектор).
Документация MSDN.Net 4.5 "нарушение"
Сразу же после публикации этого вопроса я нажал на связанный вопрос и оказался в "Совместимость приложений в .NET Framework 4.5" , в котором говорится об FromAsync
:
Изменить: реализация
IAsyncResult
должна выполняться синхронно, а свойствоCompletedSynchronously
должно возвращать trueдля завершения задачи.Воздействие. Результирующая задача не будет завершена, если реализация
IAsyncResult
не завершит синхронное выполнение, но ее СвойствоCompletedSynchronously
возвращает значение True.
По иронии судьбы (или беспричинно) страница CompletedSynchronously
гласит:
Примечания для разработчиков. Большинство исполнителей интерфейса
IAsyncResult
не будут использовать это свойство и должны возвращать false.
Стивен Туб уточнил это со следующим:
Таблица в http://msdn.microsoft.com/en-us/library/hh367887%28v=VS.110%29.aspx#core, и, в частности, описание "Изменить", неверно (...).
Изменения в .NET 4.5 до
FromAsync
произошли, но это было не то, что всеIAsyncResult.CompletedSynchronously
реализация должна возвращать true: это не имеет никакого смысла. Изменение состояло в том, чтоFromAsync
на самом деле теперь смотрит наIAsyncResult’s CompletedSynchronously
(он не смотрел у него вообще в .NET 4), и, следовательно, он ожидает, что он будет точным. В виде например, если у вас была ошибкаIAsyncResult
,FromAsync
могла все еще работали в .NET 4, тогда как с .NET 4.5 его менее вероятно для работы с ошибкой.В частности, его ok, если
IAsyncResult.CompletedSynchronously
возвращаетсяfalse
. Однако, если он возвращаетtrue
,IAsyncResult
должен иметь факт завершен синхронно. ЕслиCompletedSynchronously
возвращаетсяtrue
, ноIAsyncResult
не завершен, у вас есть ошибка, которая вам нужна и его вероятность того, чтоTask
возвращается изFromAsync
не будет выполнена правильно.Изменение было сделано по соображениям производительности.
Возврат к моему проблемному коду
Вот его очень полезный анализ, который я включаю полностью, поскольку он, вероятно, будет полезен другим разработчикам IAsyncResult
:
Проблема заключается в том, что библиотека, которую вы используете, имеет очень ошибочная реализация
IAsyncResult
; в частности, его неверно реализуяCompletedSynchronously
. Имеет реализация:public bool CompletedSynchronously { get { return _isCompleted; } } public bool IsCompleted { get { return _isCompleted; } }
В поле
_isCompleted
указано, выполняется ли асинхронная операция завершено, что хорошо, и его штраф, чтобы вернуть это изIsCompleted
, поскольку это свойство предназначено для указания того, операция завершена или нет. НоCompletedSynchronously
не может просто вернуть это же поле:CompletedSynchronously
необходимо вернуть операция завершена синхронно, то есть завершена ли она во время вызоваBeginXx
, и он должен всегда возвращать одно и то же значение для данного экземпляраIAsyncResult
.Рассмотрим стандартный шаблон для
IAsyncResult.CompletedSynchronously
. Его цель - разрешить вызывающийBeginXx
, чтобы продолжить выполнение последующей работы, а чем иметь обратный вызов из-за работы. Это особенно важно для избежания погружений в стопки (представьте себе длинную последовательность асинхронных операции, которые фактически выполнялись синхронно: если обратные вызовы обрабатывали всю работу, тогда каждый обратный вызов инициировал следующий, чей обратный вызов инициирует следующую операцию, но потому что они выполняли синхронно, их обратные вызовы также синхронно вызываться как часть методовBeginXx
, поэтому каждый вызов будет становиться все глубже и глубже в стеке, пока это потенциально переполненный):IAsyncResult ar = BeginXx(…, delegate(IAsyncResult iar) => { if (iar.CompletedSynchronously) return; … // do the completion work, like calling EndXx and using its result }, …); if (ar.CompletedSynchronously) { … // do the completion work, like calling EndXx and using its result }
Обратите внимание, что как вызывающий, так и обратный вызов используют один и тот же
CompletedSynchronously
, чтобы определить, из какого из них выполняется Перезвони. Таким образом,CompletedSynchronously
всегда должен возвращать то же самое значение для этого конкретного экземпляра. Если это не так, ошибочное поведение может легко получиться. Например, их реализацияCompletedSynchronously
возвращает эквивалентIsCompleted
. Так представьте себе следующую последовательность событий:
BeginXx
вызывается и инициирует операцию asyncBeginXx
возвращает его вызывающему, который проверяетCompletedSynchronously
, что является ложным, поскольку операция hasnt завершено еще.- Теперь операция завершается и обратный вызов вызывается. Обратный вызов видит, что
CompletedSynchronously
является истинным, и поэтому doesnt выполните любую из последующих работ, потому что он предполагает, что вызывающий абонент сделал это.- И теперь никто не запускает или не запускает обратный вызов.
Короче говоря, в библиотеке есть ошибка. Если вы изменили
CompletedSynchronously
, чтобы вернуть true, вы замаскировали эту проблему, но вы, вероятно, вызвали другое: если вызывающий (в вашем случаеFromAsync
) думает, что операция уже завершена, она немедленно вызовите методEndXx
, который будет блокироваться до асинхронного операция завершена, поэтому вы вернули свои асинхронные операции в синхронные. Вы пытались просто всегда возвращать ложные отCompletedSynchronously
вместо того, чтобы всегда возвращать true?