Итерация по асинхронному методу

Несколько связанных вопросов об асинхронной CTP:

  • Я могу выполнить итерацию по блоку итератора (IEnumerable<T> yield-return T), используя GetEnumerator(), а затем методы перечислителя MoveNext() и Current(). Какой аналог для async методов? Как метод, не вызывающий async, может получать и обрабатывать любые элементы awaited, а затем ContinueWith()? Можете ли вы привести короткий пример? Я просто не вижу этого.

  • Кроме того, в следующем примере метод async, MyAwaitable имеет метод GetAwaiter(). Если GetAwaiter() возвращает string, но THuh не является string, компилятор не жалуется. Какие типы ограничений/ожиданий существуют между THuh и GetAwaiter()?

    async Task<THuh> DoSomething()
    {
         var x = await new MyAwaitable("Foo");
    
         var y = await new MyAwaitable("Bar");
    
         return null;
    } 
    
  • Пожалуйста, объясните следующую строку проекта спецификации C#. Являются ли async Task<T> методы return default(T), которые никогда не будут использоваться? Я вижу некоторые образцы, которые, кажется, не следуют этому правилу — возвращаемое значение кажется достижимым, а значение не по умолчанию. Это значение недоступно? Если да, то почему неудобный недоступный оператор возврата?

В асинхронной функции с типом возвращаемого значения Task<T> для некоторого T операторы возврата должны иметь выражение, неявно преобразуемое в T, а конечная точка тела должна быть недоступна.

  • В спецификации говорится: «Все GetAwaiter, IsCompleted, OnCompleted и GetResult предназначены для «неблокировки»» — так в каком же методе должна быть определена (потенциально) длительная операция?

Спасибо!


person Jason Kleban    schedule 08.12.2011    source источник


Ответы (2)


Я пытаюсь преобразовать передачу продолжения, которую я сделал (неловко, но успешно) с блоками итератора, и вместо этого преобразовал его для использования асинхронного режима. Думаю, я борюсь по-новому. Понимание изменений похоже на разрывание полосок липучки шириной 3 дюйма.

Я могу понять, что вы можете чувствовать таким образом. Я не рекомендую людям пытаться построить CPS из блоков итераторов, потому что на самом деле это не очень подходит, независимо от того, какие общие механизмы лежат в основе итераторов и CPS. Блоки итераторов предназначены для быстрого создания методов, которые превращают структуры данных в последовательности или превращают последовательности в другие последовательности; они не предназначены для решения общей проблемы вызова с текущим продолжением.

Если уж на то пошло, то async/await тоже не является call-with-current-continuation, хотя, очевидно, он на порядок ближе. Async/await разработан для облегчения асинхронности на основе задач; то, что он делает это, переписывая код в форму стиля передачи продолжения, является деталью реализации.

Этот ответ, который я написал на связанную тему, может помочь:

Как реализовать новую асинхронную функцию в c# 5.0 с помощью call/cc?

Я подозреваю, что концептуальная проблема, с которой вы столкнулись, заключается в том, что в асинхронном стиле итератора "оркестратор" - вещь, которая выясняет, когда блок итератора возобновляет работу с того места, где он остановился, - является вашим код. Вы пишете какой-то код и решаете, когда вызвать MoveNext, чтобы прокачать итератор. При асинхронности на основе задач за вас это сделает какой-то другой кусок кода. Когда задача завершается, велика вероятность, что она отправляет этот факт в очередь сообщений где-то, а затем, когда очередь сообщений заполняется, активируется продолжение с результатом. В вашем коде нет явного «MoveNext», на который вы можете указать; скорее, того факта, что задача завершена и известно ее собственное продолжение, достаточно, чтобы гарантировать, что продолжение помещено в рабочую очередь для возможного выполнения.

Если у вас есть дополнительные вопросы, я рекомендую вам опубликовать их на SO и/или асинхронном форуме.

person Eric Lippert    schedule 09.12.2011
comment
Это потрясающий ответ, и вы его уловили - это был мой концептуальный разрыв. 1 Верно ли это?: Если я пишу свои собственные (ожидаемые и) ожидающие, я получаю управление, когда IsComplete, и я вызываю любые продолжения, зарегистрированные Framework с OnComplete(Action) в MyAwaiter.SetOutcome(). Я могу использовать пользовательскую TaskScheduler для написания детерминированной однофайловой (однопоточной?) политики планирования. 2 (Где) Насколько легко отказаться от дерева продолжений (при простое)? Я предполагаю, что пользовательский TaskScheduler может просто НЕ планировать больше. Какие-то последствия? - person Jason Kleban; 12.12.2011
comment
Привет Эрик, Любые мысли по 1 и 2 выше? - person Jason Kleban; 26.01.2012

В вашем примере DoSomething компилятор не жалуется, потому что тип вашего метода MyAwaitable GetResult не имеет ничего общего с THuh. Утверждение, относящееся к THuh, есть return null;. Нулевой литерал неявно преобразуется в THuh, так что все в порядке.

Ключевое слово IEnumerable, аналогичное await, называется foreach. await требует тип, соответствующий определенному шаблону, и foreach тоже. Один — это механизм для использования ожидаемых типов, другой — для использования перечислимых типов.

С другой стороны, блоки итераторов (yield return и yield break) представляют собой механизмы для определения перечислимых типов (путем написания метода, а не явного объявления типа). Аналогия здесь — ключевое слово async.

Чтобы уточнить аналогию между async и yield return, обратите внимание, что блок итератора, возвращающий IEnumerable<int>, может содержать оператор yield return 42;, а асинхронный метод, возвращающий Task<int>, может содержать оператор yield return 42;. Обратите внимание, что в обоих случаях тип возвращаемого выражения является не типом возвращаемого значения метода, а скорее аргументом типа возвращаемого типа метода.


Если вы еще этого не сделали, вам действительно следует прочитать блог Эрика Липперта по этим темам:

http://blogs.msdn.com/b/ericlippert/archive/tags/Async/

http://blogs.msdn.com/b/ericlippert/archive/tags/Iterators/

Кроме того, сообщения о стиле передачи продолжения, отличные от тех, что в серии Async, могут быть полезны, если эта концепция для вас нова (как это было для меня):

http://blogs.msdn.com/b/ericlippert/archive/tags/continuation+passing+style/


Наконец, примеры см. в сообщении в блоге Эрика со ссылкой на его статья MSDN и связанные статьи в том же выпуске и последующая статья Билла Вагнера по адресу http://msdn.microsoft.com/en-us/vstudio/hh533273

ИЗМЕНИТЬ

Я вижу некоторые образцы, которые, кажется, не следуют этому правилу — возвращаемое значение кажется достижимым, а значение не по умолчанию. Это значение недоступно? Если да, то почему неудобный недоступный оператор возврата?

Фраза «конечная точка тела должна быть недоступна» означает, что у вас должен быть оператор возврата. Конечная точка тела находится после оператора return и становится недостижимой с помощью оператора return. Пример использования обычного метода int-return:

public int Main()
{
    Console.WriteLine("X");
    //after this comment is the reachable end point of the body; this method therefore won't compile.
}

public int Main()
{
    Console.WriteLine("X");
    return 0;
    //anything after the return statement is unreachable, including the end point of the body; this method therefore will compile.
}

ИЗМЕНИТЬ 2

Вот короткий, тривиальный пример ожидающего, который вычисляет последнюю половину строки. В примере передается продолжение, которое выводит результат на консоль. Это не потокобезопасно!

public static class StringExtensions
{
    public static SubstringAwaiter GetAwaiter(this string s)
    {
        return new SubstringAwaiter(s, s.Length / 2, s.Length - s.Length / 2);
    }
}

public class SubstringAwaiter
{
    private readonly string _value;
    private readonly int _start;
    private readonly int _length;
    private string _result;
    private Action _continuation;

    public SubstringAwaiter(string value, int start, int length)
    {
        _value = value;
        _start = start;
        _length = length;
    }

    public bool IsCompleted { get; private set; }
    public void OnCompleted(Action callback)
    {
        if (callback == null)
            return;

        _continuation += callback;
    }
    public string GetResult()
    {
        if (!IsCompleted)
            throw new InvalidOperationException();
        return _result;
    }
    public void Execute()
    {
        _result = _value.Substring(_start, _length);
        IsCompleted = true;
        if (_continuation != null)
            _continuation();
    }
}

public class Program
{
    public static void Main()
    {
        var awaiter = "HelloWorld".GetAwaiter();
        awaiter.OnCompleted(() => Console.WriteLine(awaiter.GetResult()));
        awaiter.Execute();
    }
}
person phoog    schedule 08.12.2011
comment
Спасибо за ответ. Какой аналог GetEnumerator(), MoveNext() и Current()? Я не думаю, что вы можете await использовать метод, который сам по себе не является async. Как я могу вызвать асинхронный метод из неасинхронного метода очень контролируемым образом, чтобы получить доступ ко всем элементам awaited для обработки? Что-то связанное с DoSomething().GetAwaiter() Я думаю, но я не понимаю, как его использовать. Когда я пытаюсь запустить var doSomething = DoSomething();, DoSomething запускается до первого await, но никогда не продолжается, и GetResult() Awaiter никогда не вызывается. - person Jason Kleban; 08.12.2011
comment
Аналогом GetEnumerator является GetAwaiter. Аналогом { MoveNext и Current } является { IsCompleted, OnCompleted и GetResult }. См. блог Марка Гравелла: marcgravell.blogspot.com/2011/04/musings. -on-async.html. - person phoog; 08.12.2011
comment
Можете ли вы привести краткий пример вызова асинхронности и перехвата ожидаемых значений и ContineWith()ing? Пример кода, прикрепленный к блогу Gravell, кажется более сложным, чем необходимо для этого предположительно простого случая. - person Jason Kleban; 08.12.2011
comment
@uosɐſ достаточно честно; Я также добавил немного, чтобы объяснить недостижимую конечную точку - person phoog; 08.12.2011
comment
Это тоже отличный ответ. Спасибо! - person Jason Kleban; 09.12.2011