async & await - опрос альтернатив [закрыт]

15

Теперь, когда мы знаем, что ожидает c # 5, нам, видимо, еще есть возможность повлиять на выбор двух новых ключевых слов для « асинхронности », которые были анонсированы Андерсом Хейсбергом вчера на PDC10 .

async void ArchiveDocuments(List<Url> urls) {
    Task archive = null;
    for(int i = 0; i < urls.Count; ++i) {
        var document = await FetchAsync(urls[i]);
        if (archive != null)
            await archive;
        archive = ArchiveAsync(document);
    }
}

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

Пожалуйста - одно предложение за ответ, дубликаты будут уничтожены.

Benjol
источник
Кстати, «встроенное в язык асинхронное программирование» дает нам LIAP, но не совсем скатывается с языка так же, как LINQ;)
Benjol
1
Если это не явный скачок.
Конрад Фрикс
3
Но «Асинхронная среда выполнения, встроенная в язык», сокращается.
Гленатрон
Это должно быть не по теме.
DeadMG

Ответы:

6

Учитывая, что я не совсем понимаю значение / необходимость async, я не могу спорить с этим, но мое лучшее предложение для замены await:

yield while (смотрите! нет новых ключевых слов)

Обратите внимание , подумав об этом немного больше, я задаюсь вопросом, является ли повторное использование whileтаким способом хорошей идеей - естественной тенденцией будет ожидание логического значения после этого.

(Думает: поиск хороших ключевых слов - это как поиск хороших доменных имен :)

Benjol
источник
+1 и, кстати, ты побил меня за то, что я комментировал его запись в блоге на 7 минут ...
Обратите внимание на себя - придумай имя
Но вы не обязательно уступаете исполнению, если задача уже выполнена. Но вы всегда ждете завершения задачи (хотя никогда не ждете).
Аллон Гуралнек
@Allon Вы не обязательно должны запускать тело цикла while(x) {...}, если xfalse.
Примечание для себя - придумайте имя
@ Примечание: ну нет глагола в while. Если вы добавляете глагол, например do, вы получаете do {...} while (x), который выполняет тело независимо от x (хотя бы один раз). Ваше предположение yield whileкажется очень похожим do while, но с противоположными гарантиями выполнения глагола, что может быть немного вводящим в заблуждение (но не так уж и важно). Больше всего мне не нравится то yield, что это подразумевает реализацию механизма. Весь смысл async/ в awaitтом, что вы пишете асинхронную операцию в синхронном стиле. yieldнарушает этот синхронный стиль.
Аллон Гуралнек
Новое ключевое слово обязательно плохо? Насколько я понимаю, awaitключевое слово будет распознаваться контекстом, поэтому вы все равно можете иметь метод или переменную с именем «await», если хотите. В некоторой степени, я думаю, что использование нового ключевого слова для новой функциональности менее запутанно, чем повторное использование существующего ключевого слова для обозначения нескольких вещей. (преувеличенный пример: dangermouse.net/esoteric/ook.html )
Тим Гудман
5

Как насчет отсутствия ключевого слова?

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

Document doc = DownloadDocumentAsync();

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

Обновить

Первоначально я предложил компилятору разобраться с выводом типа, чтобы понять, что делать. Подумав об этом дальше, я бы сохранил существующую реализацию в CTP как есть, но сделаю пару тривиальных дополнений к ней, чтобы уменьшить случаи, когда вам нужно будет использоватьawait явно ключевое слово.

Мы изобретаем атрибут: [AutoAwait]. Это может быть применено только к методам. Один из способов применить это к вашему методу - пометить его async. Но вы также можете сделать это вручную, например:

[AutoAwait]
public Task<Document> DownloadDocumentAsync()

Затем внутри любого asyncметода компилятор будет предполагать, что вы хотите ожидать вызова DownloadDocumentAsync, поэтому вам не нужно его указывать. Любой вызов этого метода будет автоматически ожидать его.

Document doc = DownloadDocumentAsync();

Теперь, если вы хотите «стать умным» и получить Task<Document>, вы используете оператор start, который может появиться только перед вызовом метода:

Task<Document> task = start DownloadDocumentAsync();

Я думаю, аккуратно. Теперь простой вызов метода означает то, что он обычно означает: дождитесь завершения метода. И startуказывает на что-то другое: не ждите.

Для кода, который появляется за пределами asyncметода, единственный способ, которым вы можете вызывать [AutoAwait]метод - это префикс его start. Это заставляет вас писать код, имеющий одинаковое значение, независимо от того, появляется он в asyncметоде или нет.

Тогда я начинаю жадничать! :)

Во-первых, я хочу asyncприменить методы интерфейса:

interface IThing
{
    async int GetCount();
} 

В основном это означает, что метод реализации должен возвращать Task<int>или что-то совместимое с await, и вызывающие методы получат [AutoAwait]поведение.

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

async int GetCount()

Так что я не должен упоминать Task<int>в качестве типа возврата.

Кроме того, я хочу asyncприменить к типам делегатов (которые, в конце концов, похожи на интерфейсы с одним методом). Так:

public async delegate TResult AsyncFunc<TResult>();

asyncДелегат имеет - вы уже догадались - [AutoAwait]поведение. Из asyncметода вы можете вызывать его, и он будет автоматически awaitредактироваться (если вы не решите просто startего). И так, если вы скажете:

AsyncFunc<Document> getDoc = DownloadDocumentAsync;

Это просто работает. Это не вызов метода. Ни одна задача еще не была запущена - это async delegateне задача. Это фабрика для постановки задач. Ты можешь сказать:

Document doc = getDoc();

И это запустит задачу и будет ждать ее завершения и даст вам результат. Или вы можете сказать:

Task<Document> t = start getDoc();

Таким образом, единственное место в этом, где просачивается «сантехника», это то, что если вы хотите создать делегат для asyncметода, вы должны знать, чтобы использовать async delegateтип. Так что вместо Funcтебя надо сказать AsyncFuncи так далее. Хотя однажды такие вещи можно исправить с помощью улучшенного вывода типа.

Другой вопрос, что должно произойти, если вы скажете начать с обычного (не асинхронного) метода. Очевидно, что ошибка компиляции будет безопасным вариантом. Но есть и другие возможности.

Дэниел Уорвикер
источник
Это может быть выполнимо с неявным преобразованием, но в противном случае потребуется оценка оператора слева направо (что в точности противоположно тому, как обычно работает компилятор, за исключением лямбда-выражений). Я думаю, что я все еще был бы против этого, потому что это предотвращает использование var, возможно, необходимости заменить какое-то длинное явное имя типа, а также неоднозначно между awaitслучаем и случаем, когда кто-то случайно вызвал асинхронный метод вместо обычного синхронного метода. Сначала это кажется интуитивно понятным, но на самом деле нарушает принцип наименьшего удивления.
Aaronaught
@Aaronaught - Почему это мешает использованию var? Интересно, отвечаете ли вы на предыдущую редакцию моего ответа ... Я полностью переписал его. Теперь вы можете думать об этом предложении следующим образом: если метод помечен специальным атрибутом, это как если бы awaitключевое слово автоматически вставлялось перед вызовами этого метода (если вы не подавите это с помощью startпрефикса). Все остается точно так же, как в CTP, и, следовательно, varработает нормально.
Даниэль Эрвикер
На самом деле мне было ... странно, что я решил вернуться к этой теме и ответить на ваш ответ почти в то же время, когда вы решили отредактировать его. Мне придется перечитать это сейчас ...
Aaronaught
1
Мне нравится твоя инверсия ключевого слова await. И мне также не нравится тройная избыточность public async Task<int> FooAsync().
Аллон Гуралнек
1
Да, я вижу, что соглашение об именовании Async-postfix является признаком того, что что-то может быть захвачено более формально. По сути, если есть правило, гласящее, что «подобные методы должны называться определенным образом, чтобы люди знали, как их правильно вызывать», то из этого следует, что это же правило можно использовать для атрибута этих методов определенным образом, а затем компилятор помочь вам правильно их назвать.
Даниэль Эрвикер
4
hearken unto AsyncFetch(…)

(если вы не получите его, прочитайте запись Эрика в блоге . По крайней мере, это лучше, чем for sooth Romeo wherefore art thou AsyncFetch(…))

Примечание для себя - придумайте имя
источник
(те, у кого нет чувства юмора, не должны подавать заявку)
Примечание для себя - придумайте имя
4

Я думаю, что asyncэто хорошо, но, возможно, это потому, что я связываю это с асинхронными страницами ASP.NET - та же идея.

Для awaitключевого слова я предпочитаю continue afterили resume after.

Мне не нравится yieldни один из его вариантов, потому что семантика такова, что метод может никогда не привести к выполнению; это зависит от состояния задачи.

Aaronaught
источник
Мне нравится resume afterза await. Может asyncбыть, можно назвать resumable.
Тим Гудман
Хотя я предпочел бы просто after, у continue afterподхода есть сильное преимущество реализации: он включает в себя существующее в настоящее время контекстное ключевое слово, но с синтаксисом, который не совместим с текущим использованием. Это гарантирует, что дополнение никогда не нарушит существующий код. При использовании совершенно нового ключевого слова реализация должна справляться с возможным использованием слова в качестве идентификатора в более старом коде, что может быть довольно сложно.
Эдурн Паскуаль
3

Я добавил к комментариям в блоге Эрика тоже, я не вижу проблем с использованием того же ключевого слова async

var data = async DownloadFileAsync(url);

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

var data = async DownloadFile(url);

в отличие от вызова синхронного

var data = DownloadFile(url);

Черт возьми, мы также должны иметь возможность определять их одинаково, поскольку в нашем объявлении есть ключевое слово async, почему мы должны вручную добавлять «Async» к каждому имени метода - компилятор может сделать это за нас.

Марк Н
источник
Мне нравится добавленный вами сахар, хотя ребята из C #, вероятно, не пойдут на это. (FWIW, они уже делают нечто подобное при поиске имен атрибутов)
Примечание для себя - придумайте имя
2
И учитывая, что asyncключевое слово в методе - просто изящность (если я правильно понял), я задаюсь вопросом, не лучше ли было бы сделать противоположность тому, что вы предлагаете: отказаться asyncот метода on и просто использовать его где они сейчас есть await.
Бенджол
Это возможность. Ключевое слово async в методе delcaration как раз для чистоты. Я бы предпочел, чтобы он там хранился, но без необходимости добавлять «Async» к именам методов. Например, async Task<Byte[]> DownloadFile(...)а не Task<Byte[]> DownloadFileAsync(...)(последняя будет скомпилированной подписью в любом случае). В любом случае работает.
Марк Х
Я, честно говоря, тоже не фанат этого. Как и в предыдущем комментарии, я должен отметить, что ваша окончательная версия нарушает принцип наименьшего удивления, так как на самом деле он вызывает метод, совершенно отличающийся от написанного, и реализация и сигнатура этого метода полностью зависят от реализующего класса. , Даже первая версия проблематична, потому что она ничего не говорит (асинхронно выполняет этот асинхронный метод?). Мысль, которую мы пытаемся выразить, является продолжением или отложенным исполнением, и это вовсе не выражает этого.
Aaronaught
3

async = task - это изменение функции для возврата задачи, так почему бы не использовать ключевое слово "task"?

await = finish - нам не обязательно ждать, но задача должна "закончиться", прежде чем использовать результат.

Джон Фишер
источник
Здесь очень сложно поспорить с простотой.
Сбл
2

Мне нравится yield until. yield whileКак уже говорилось, это здорово, и не вводит никаких новых ключевых слов, но я думаю, что «пока» не поймет поведение немного лучше.

Я думаю, что yield <something>это отличная идея, потому что yield уже отражает идею превращения оставшейся части метода в продолжение. Может быть, кто-то может придумать лучшее слово, чем «до».

nlawalker
источник
2

Я просто хочу зарегистрировать свой голос за предложение Аарона Дж comefrom- первое соответствующее использование заявления COMEFROM от INTERCAL . Идея состоит в том, что это своего рода противоположность GOTO (отходит от оператора GOTO) в том, что в вашем коде происходит переход к команде COMEFROM.

Гейб
источник
2

Поскольку мы имеем дело с Task<T>s, как насчет использования startв качестве ключевого слова перед оператором, как в:

start var document = FetchAsync(urls[i]);

главный герой
источник
Хм, может finishбыть, даже лучше, чем start?
Главный герой
1

Стоит отметить, что F # также использует asyncключевое слово в своих асинхронных рабочих процессах, что в значительной степени совпадает с новой функциональностью асинхронности в C # 5. Поэтому я бы сохранил это

Для awaitключевого слова в F # они просто используют let!вместо let. C # не имеет такой же синтаксис присваивания, поэтому им нужно что-то справа от =знака. Как сказал Бенджол, он функционирует так же, как yieldи должен быть почти таким вариантом.

Скотт Уитлок
источник
1
«Ожидание» вообще не обязательно должно быть в присваивании (хотя, конечно, обычно это так). Это допустимо в качестве оператора практически для любого выражения, имеющего тип, в котором мы можем найти GetAwaiter. (Точные правила еще не разработаны в публикуемой форме.)
Эрик Липперт,
1
@ Эрик, в F # это было бы do!, но ты знал это ...
Бенджол
1

yield async FetchAsync(..)


Это прекрасно сочетается с asyncмодификатором, который вам нужен для метода, который вы вызываете. А также семантика текущего, yield returnто есть вы возвращаете выполнение и передаете его перечисляющему коду, в этом случае вы уступаете свое выполнение асинхронному методу.

Представьте, что в будущем появятся другие варианты использования yield, мы могли бы добавить, yield xгде x - это блестящая новая функция вместо того, чтобы все эти разные ключевые слова выполняли в основном одно и то же, принося результат.

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

И хорошо, если asyncвозврат выполняется синхронно, но наличие ключевого слова должно означать, что есть вероятность того, что метод будет работать асинхронно и что вы уступите выполнение другому методу. Ваш метод должен учитывать это независимо от того, выполняет ли метод асинхронные вызовы или нет.

ИМО Я думаю, что различные «не приносящие» случаи являются деталями реализации. Я предпочел бы ручаться за последовательность в языке (то есть повторное использование yield).

chakrit
источник
0

Как насчет того complete, как в «Я хочу, чтобы задача была выполнена»?

Task<byte[]> downloadTask = DownloadFileAsync(url);
byte[] data = complete downloadTask;
Аллон Гуралнек
источник
1
Почему отрицательный голос? Это вежливость, чтобы хотя бы объяснить себя после подавления.
Аллон Гуралнек
0

task(для объявления метода) и async(в теле метода)

sblom
источник
Этот ответ немного запоздал, они остановились на асинхронных и ждут год назад.
Джей Элстон