«Итерация» по асинхронному методу - PullRequest
2 голосов
/ 08 декабря 2011

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

  • Я могу перебрать блок итераторов (IEnumerable<T> возвращающий доход T), используя GetEnumerator(), а затем методы перечисления MoveNext() и Current(). Что является аналогом для async методов? Как может не async вызывающий метод получать и обрабатывать любые await редактируемые элементы, а затем 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 a default(T), которые никогда не будут использоваться? Я вижу некоторые примеры, которые, кажется, не следуют этому правилу - возвращаемое значение кажется достижимым, и значение не по умолчанию. Это значение недоступно? Если так, то почему неуклюжий недоступный оператор возврата?

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

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

Спасибо!

Ответы [ 2 ]

7 голосов
/ 09 декабря 2011

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

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

В этом отношении async / await также не является точно вызовом с текущим продолжением, хотя это и порядоквеличины ближе, очевидно.Async / await предназначена для упрощения асинхронности на основе задач;то, что он делает это, переписывая код в форму стиля передачи продолжения, является подробностью реализации.

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

Как может новый асинхронный режимвозможность в c # 5.0 быть реализована с помощью call / cc?

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

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

1 голос
/ 08 декабря 2011

В вашем примере DoSomething компилятор не жалуется, потому что тип метода GetResult вашего MyAwaitable не имеет ничего общего с 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

EDIT

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

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

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();
    }
}
...