2015-02-06 1 views
3

Рассмотрим следующие коды C

var L1 = 
Task.Run(() => 
{ 
    return Task.Run(() => 
    { 
     return Task.Run(() => 
     { 
      return Task.Run(() => 
      { 
       return new Dummy(); 
      }); 
     }); 
    }); 
}); 

var L2 = 
Task.Run(async() => 
{ 
    return await Task.Run(async() => 
    { 
     return await Task.Run(async() => 
     { 
      return await Task.Run(async() => 
      { 
       return new Dummy(); 
      }); 
     }); 
    }); 
}); 

var L3 = 
Task.Run(async() => 
{ 
    return Task.Run(async() => 
    { 
     return Task.Run(async() => 
     { 
      return Task.Run(async() => 
      { 
       return new Dummy(); 
      }); 
     }); 
    }); 
}); 


var r1 = L1.Result; 
var r2 = L2.Result; 
var r3 = L3.Result; 


# ===================== =======================================
на первый взгляд, L1, L2 и L3 все выглядит как
Task<Task<Task<Task<Dummy>>>>
Оказывается, L1 и L2 просто Task<Dummy>
Так, я смотрю на MSDN для Task.Run
(мой перегруженный Task.Run является: Task.Run<TResult>(Func<Task<TResult>>))
MSDN говорит:странное поведение компилятор при смешивании Task.Run с асинхронным/ждут ключевого слова

Возвращаемое значение является следующим: Задача (TResult), который представляет собой прокси-сервер для выполнения этой задачи (TResult), возвращаемой функцией.

Он также говорит, что в примечаниях:

Метод Run<TResult>(Func<Task<TResult>>) используется языком компиляторов для поддержки асинхр и ждет ключевых слов. Не предполагается, что вызывается непосредственно из кода пользователя.


Таким образом, похоже, что я не должен использовать этот перегруженный Task.Run в моем коде,
и если я делаю, компилятор удалит лишние слои Task<Task<Task<...<TResult>>>> и просто дать вам Task<TResult>
If указателя мыши над L1 и L2, он говорит вам, что это Task<Dummy>, не мой
ожидается Task<Task<Task<Task<Dummy>>>>

до сих пор так хорошо, пока я не смотрю на L3
L3 выглядит почти точно так же, как L1 и L2. Разница заключается в том:
L3 имеет только async ключевое слово, а не await, в отличие от L1 и L2, где L1 имеет ни один из них и L2 имеет оба

Удивительно, L3 теперь рассматривается как Task<Task<Task<Task<Dummy>>>>, в отличие от L1 и L2 , где оба считаются Task<Dummy>

Мои вопросы:
1.
Что заставляет компилятор для лечения L3 отличается от L1 и L2. Почему просто добавление 'async' в L1 (или удаление await из L2) заставляет компилятор обрабатывать его по-разному?


2.
Если вы больше каскадируете Task.Run to L1/L2/L3, значит, сбой Visual Studio. Я использую VS2013, если я каскадирую 5 или более слоев Task.Run, он сработает. 4 слоя - это лучшее, что я могу получить, поэтому в качестве примера я использую 4 слоя. Это только я ? Что случилось с компилятором при переводе Task.Run?

Благодаря

+0

У меня 7 уровней глубокой, а затем VS2013 не реагировал. Это может быть один из моих расширений (в частности, Resharper), который виноват; Я не дошел до этого. –

ответ

6

Что заставляет компилятор для лечения L3 отличается от L1 и L2. Почему просто добавление «async» в L1 (или удаление ожидания из L2) заставляет компилятор относиться к нему по-другому?

Потому что async и await меняют типы в лямбда-выражениях. Вы можете представить async как «добавление» обертки Task<> и await как «удаление» обертки Task<>.

Просто рассмотрите типы, связанные с самыми внутренними вызовами. Во-первых, L1:

return Task.Run(() => 
{ 
    return new Dummy(); 
}); 

Тип () => { return new Dummy(); } является Func<Dummy>, и поэтому тип возвращаемого that overload of Task.Run является Task<Dummy>.

Таким образом, тип () => ###Task<Dummy>### - Func<Task<Dummy>>, который вызывает different overload of Task.Run, с обратным типом Task<Dummy>. И так далее.

Теперь рассмотрим L2:

return await Task.Run(async() => 
{ 
    return new Dummy(); 
}); 

Тип async() => { return new Dummy(); } является Func<Task<Dummy>>, поэтому тип возвращаемого that overload of Task.Run является Task<Dummy>.

Тип async() => await ###Task<Dummy>### - Func<Task<Dummy>>, поэтому он вызывает same overload of Task.Run с типом результата Task<Dummy>. И так далее.

Наконец, L3:

return Task.Run(async() => 
{ 
    return new Dummy(); 
}); 

Тип async() => { return new Dummy(); } снова Func<Task<Dummy>>, поэтому тип возвращаемого that overload of Task.Run является Task<Dummy>.

Тип async() => { return ###Task<Dummy>### }: Func<Task<Task<Dummy>>>. Обратите внимание на вложенную задачу. Итак, same overload of Task.Run вызывается снова, но его тип возврата - Task<Task<Dummy>>.

Теперь вы просто повторяетесь для каждого уровня. Тип async() => { return ###Task<Task<Dummy>>### }: Func<Task<Task<Task<Dummy>>>>. same overload of Task.Run вызывается снова, но его тип возврата Task<Task<Task<Dummy>>> на этот раз. И так далее.

Если вы каскадируете больше задач. Перейдите на L1/L2/L3, сбой в работе Visual Studio. Я использую VS2013, если я каскадирую 5 или более слоев Task.Run, он сработает. 4 слоя - это лучшее, что я могу получить, поэтому в качестве примера я использую 4 слоя. Это только я ? Что случилось с компилятором при переводе Task.Run?

Кому это нужно? Никакой код реального мира никогда не сделает это. Существуют хорошо известные сценарии, которые чрезвычайно сложны для компилятора в разумные сроки; using lambda expressions in method overload resolution is one. Вложенные вызовы с использованием лямбда-выражений makes the compiler work exponentially harder.

+1

Удивительный ответ.! –

+0

Удивительный ответ x 2! У меня больше нет суеты, спасибо –

Смежные вопросы