Подтвердить что ты не робот

.Net 4.5 убил мой TPL, теперь что?

Иллюстрация 1: некоторый код, переносящий сетевой вызов Async (не async!) в Task

public static Task<byte[]> GetAsync(IConnection connection, uint id)
{
    ReadDataJob jobRDO = new ReadDataJob();

    //No overload of FromAsync takes 4 extra parameters, so we have to wrap
    // Begin in a Func so that it looks like it takes no parameters except 
    // callback and state
    Func<AsyncCallback, object, IAsyncResult> wrapped = (callback, state) =>
                jobRDO.Begin(connection, 0, 0, id, callback, state);

    return Task<byte[]>.Factory.FromAsync(wrapped, ar =>
    {
        ErrorCode errorCode;
        UInt32 sError;
        UInt32 attribute;
        byte[] data = new byte[10];
        jobRDO.End(out errorCode, out sError, out attribute, out data);
        if(error != ErrorCode.NO_ERROR)  throw new Exception(error.ToString());
        return data;
    }, jobRDO);
}

Установка .Net 4.5 (не указывая на него VS, и не перекомпиляция) прекращает эту работу. Обратный вызов никогда не вызывается.

Любые идеи, что может быть причиной этого, и в остальном, что я могу сделать, чтобы попытаться сузить основную причину проблемы или обойти ее?

4b9b3361

Ответ 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 вызывается и инициирует операцию async
  • BeginXx возвращает его вызывающему, который проверяет CompletedSynchronously, что является ложным, поскольку операция hasnt завершено еще.
  • Теперь операция завершается и обратный вызов вызывается. Обратный вызов видит, что CompletedSynchronously является истинным, и поэтому doesnt выполните любую из последующих работ, потому что он предполагает, что вызывающий абонент сделал это.
  • И теперь никто не запускает или не запускает обратный вызов.

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